【问题标题】:Ideas on getting rid of boiler plate code摆脱样板代码的想法
【发布时间】:2019-06-24 08:11:20
【问题描述】:

我的问题非常具体。我有以下要求,出于几个原因,我需要从父类设置子类中存在的成员变量。我的计划是在构造时将 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 宏然后使用 INIT_VAL 宏传递每个变量的 setter 方法有点烦人。这可以在一个宏中完成吗(可能在 DEFINE_VAL 中)?或者关于自动注册成员名称和函数指针到父类的任何想法?

如果有任何其他想法来满足我的要求,我也将不胜感激。

【问题讨论】:

  • 这是一个占位符还是真的是... in parent({ INIT_VAL(iVal), ... })
  • 如果你不使用宏而是写child() : member( convert_to_value(iVal) ) {},那么样板就更少了...
  • 您想要的在当前的代码形式中是不可能实现的。您可以通过使用函数来摆脱宏,但仅此而已。只能在parent的构造函数中指定映射,限制性很大,使用命名成员就更严格了,因为C++中没有类型自省。
  • @foreknownas_463035818 如果你不使用宏,你至少得有convert_to_value("iVal", iVal)之类的东西。
  • @LightnessRacesinOrbit 我认为这不是 OP 真正想要的......这看起来更像是某种交换/存储数据结构的通用方式,例如 parent 是某种JSON接口,然后你扩展它以便你可以读写它。我不是 OP,所以我可能错了,但这看起来很像。

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


【解决方案1】:

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

当调用父类构造函数时,派生类和它的成员还没有被初始化,而且,迂腐地,它们还不存在。因此,无法从其基类构造函数设置派生类成员。


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

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

boost::lexical_cast&lt;T&gt; 可用于将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

【讨论】:

  • 我认为OP不想在基类构造函数中初始化派生类的变量......至少问题中没有提到这一点。 OP 想要为父类提供一种设置派生类元素的方法,可能是在对象完全构造之后。
  • 只是为了澄清,我的意思是,我在构造时将子类 setter 方法的函数指针传递给父类。不过你提出了很好的观点。
【解决方案2】:

只需使用一个虚函数来设置它,然后将地图移动到孩子身上,因为它确实应该是一个实现细节。这样,父类实际上与成员的设置方式没有任何关系。

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)
};

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-04-09
    • 1970-01-01
    • 1970-01-01
    • 2011-08-12
    • 1970-01-01
    相关资源
    最近更新 更多