摆脱样板代码的想法

时间:2019-06-24 08:11:20

标签: c++ c++11 reflection macros

我的问题很具体。我有以下要求,出于某些原因,我需要设置父类的子类中存在的成员变量。我的计划是将setter方法(存在于子类中)的函数指针(以字符串作为参数)传递给构造时的父类。父类定义一个公共方法,该方法将成员名称和值字符串作为参数,并使用值字符串调用成员的函数指针。父类可以存在于dll或lib中,并且不能访问任何转换或工厂方法,因此setter方法必须在子类中定义。

由于父类可以是其他类的基类,因此我编写了一些如下所示的宏:

#define DEFINE_VAL(Type, memberName) \
        private: \
            Type memberName; \
            void set##memberName(std::string const& val) { \
                memberName = convert_to_val(val); /* this will be a call to factory which converts string to value type*/\
/* or call to local implementation for conversion*/
            }; \

#define INIT_VAL(memberName) \
            { memberName, \
            [&](std::string const& val) { set##memberName(val); }}

父类和子类如下:

// parent.h probably in dll
class parent
{
public:
    parent(std::map<std::string, std::function<void(std::string const&)>>& m)
        : m(m)
    { }
        ... 
private:
    std::map<std::string, std::function<void(std::string const&)>> m;
};

// child.h
class child : public parent
{
public:
    child() : parent({ INIT_VAL(iVal), ... })
    { }
private:
    DEFINE_VAL(int, iVal);
        ...
};

子类可以定义许多变量,首先使用DEFINE_VAL宏,然后将每个变量的setter方法与INIT_VAL宏一起传递,这有点烦人。可以在一个宏中执行此操作(可能在DEFINE_VAL中)吗?或有关自动注册成员名称和指向父类的函数指针的任何想法?

对于实现我的要求的其他选择,我也将不胜感激。

2 个答案:

答案 0 :(得分:2)

  

出于某些原因,我需要设置父类的子类中存在的成员变量。我的计划是传递setter方法的函数指针(存在于子类中),该函数指针将字符串作为参数传递给构造时的父类

当调用父类构造函数时,派生类及其成员尚未初始化,而且,就其学问而言,它们还不存在。因此,无法从其基类构造函数设置派生类成员。


一种解决方案是使用虚拟函数按名称设置成员。

在当前的C ++中没有内置的反射,要将名称与数据成员相关联并生成成员访问器,最佳实践仍然是使用宏。为此,最好的宏之一是BOOST_HANA_DEFINE_STRUCT

boost::lexical_cast<T>可用于将std::string转换为任何T

具有深入和多重继承支持的工作示例:

#include <boost/hana/define_struct.hpp>
#include <boost/hana/accessors.hpp>
#include <boost/hana/for_each.hpp>
#include <boost/hana/concat.hpp>
#include <boost/hana/length.hpp>

#include <boost/lexical_cast.hpp>

#include <unordered_map>
#include <functional>
#include <iostream>

namespace hana = boost::hana;

struct MemberSetter {
    // Using void* to reduce the number of template instantiations.
    using SetterFn = std::function<void(void*, std::string const&)>;
    using Setters = std::unordered_map<std::string, SetterFn>;

    Setters setters_;

    template<class Derived, class Accessors>
    MemberSetter(Derived* that, Accessors& accessors) {
        hana::for_each(accessors, [this](auto const& pair) {
            auto setter = [accessor = hana::second(pair)](void* vthat, std::string const& value) {
                auto* that = static_cast<Derived*>(vthat);
                auto& member = accessor(*that);
                member = boost::lexical_cast<std::remove_reference_t<decltype(member)>>(value);
            };
            auto name = hana::first(pair);
            setters_.emplace(std::string(hana::to<char const*>(name), hana::length(name)), std::move(setter));
        });
    }

    bool findAndSetMember(void* that, std::string const& name, std::string const& value) const {
        auto setter = setters_.find(name);
        if(setter != setters_.end()) {
            (setter->second)(that, value);
            return true;
        }
        return false;
    }
};

struct A {
    virtual ~A() = default;
    virtual bool setMember(std::string const& name, std::string const& value) = 0;
};

struct B : A {
    BOOST_HANA_DEFINE_STRUCT(B,
        (int, a),
        (double, b)
        );

    bool setMember(std::string const& name, std::string const& value) override {
        constexpr auto accessors = hana::accessors<B>();
        static MemberSetter const setter(this, accessors);
        return setter.findAndSetMember(this, name, value);
    }
};

struct C : B {
    BOOST_HANA_DEFINE_STRUCT(C,
        (std::string, c)
        );

    bool setMember(std::string const& name, std::string const& value) override {
        constexpr auto accessors = hana::concat(hana::accessors<B>(), hana::accessors<C>()); // Join with members of the base class.
        static MemberSetter const setter(this, accessors);
        return setter.findAndSetMember(this, name, value);
    }
};

int main() {
    C c;
    c.setMember("a", "1");
    c.setMember("b", "2.3");
    c.setMember("c", "hello");
    std::cout << c.a << ' ' << c.b << ' ' << c.c << '\n';
}

输出:

1 2.3 hello

答案 1 :(得分:1)

只需使用虚拟函数进行设置,然后将地图移至子级,因为它实际上应该是实现细节。这样,父类实际上与成员的设置没有任何关系。

class parent
{
public:
    virtual ~parent() = default;
protected:
    virtual void do_set(const std::string& name, const std::string& value) = 0;
private:
    void set(const std::string& name, const std::string& value) {
        do_set(name, value);
        // Do synchronization here
    }
};

class child : public parent
{
protected:
    void do_set(const std::string& name, const std::string& value) override {
        child::setter_map.at(name)(*this, value);
    }
private:
    int iVal;

    static const std::map<std::string, void(*)(child&, const std::string&)> setter_map;
};

#define INIT_VAL(NAME, ...) { #NAME, [](child& c, const std::string& value) __VA_ARGS__ }

const std::map<std::string, void(*)(child&, const std::string&)> child::setter_map = {
    INIT_VAL(iVal, {
      c.iVal = convert_to_val(value);
    }),
    // Init other members
};

从这开始,您也许可以找到实现set的更好方法(也许可以使用简单的if (name == ...) ... else if (name == ...) ...

或者,如果您不想使用运行时多态性,至少不要在每个parent实例中都存储一个映射。存储对全局映射的引用(这将类似于vtable本身):

class parent
{
public:
    parent() = delete;
protected:
    using setter_map = std::map<std::string, void(*)(parent&, const std::string&)>;
    parent(const setter_map& child_smap) noexcept : smap(&child_smap) {};
private:
    void set(const std::string& name, const std::string& value) {
        smap->at(name)(*this, value);
        // Do synchronization here
    }

    const setter_map* smap;
};

class child : public parent {
public:
    child() : parent(smap) {};
private:
    int iVal;

    static const setter_map smap;
};

#define INIT_VAL(NAME, ...) { #NAME, \
    [](parent& _c, const std::string& value) { \
        child& c = static_cast<child&>(_c); \
        __VA_ARGS__ \
    } \
}

const child::setter_map child::smap = {
    INIT_VAL(iVal, {
        c.iVal = convert_to_val(value);
    }),
    // (Other member setters here)
};

#undef INIT_VAL

// Or having the setters inside the class, like in your original code

class child2 : public parent {
public:
    child2() : parent(smap) {};
private:
    int iVal;
    void set_iVal(const std::string& value) {
        iVal = convert_to_val(value);
    }

    // Using a macro (Probably don't need the macros here, writing out a setter is more clear)
    template<class T>
    using type = T;
#define DEFINE_VAL(TYPE, NAME, ...) \
    void set_ ## NAME (const std::string& value) { \
        __VA_ARGS__ \
    } \
    type<TYPE> NAME

    DEFINE_VAL(float, fVal, {
        fVal = convert_val_to_float(value);
    });

    DEFINE_VAL(char[2], charArrVal, {
        charArrVal[0] = value[0];
        charArrVal[1] = value[1];
    });

    static const setter_map smap;
};

#define INIT_VAL(NAME) { #NAME, [](parent& p, const std::string& value) { static_cast<child2&>(p).set_ ## NAME (value); } }
const child2::setter_map child2::smap = {
    INIT_VAL(iVal), INIT_VAL(fVal), INIT_VAL(charArrVal)
};
#undef INIT_VAL

// Or if `convert_to_val(value)` is literally the body of every setter, that simplifies the `INIT_VAL` macro

class child3 : public parent {
public:
    child3() : parent(smap) {};
private:
    int iVal;

    static const setter_map smap;
};

#define INIT_VAL(NAME) { #NAME, [](parent& p, const std::string& value) { static_cast<child3&>(p). NAME = convert_to_val(value); } }

const child3::setter_map child3::smap = {
    INIT_VAL(iVal)
};