使用宏生成代码:具有成员和构造函数的类

时间:2013-05-08 19:14:13

标签: c++ c-preprocessor metaprogramming code-generation

假设我要定义以下结构的类:

struct MyClass {
    int x;
    bool y;
    float z;
    MyClass(QVariantMap data) : x(data["x"]), y(data["y"]), z(data["z"]) {}
};

正如您所看到的,我有一个QVariantMap(对于那些不熟悉Qt的人来说类似于std::map<std::string, boost::variant<...>>),我希望能够构建这样一个类型,而不了解它的字段,因此,方便的构造函数应该从地图“反序列化”字段。

我需要这种风格的几个类,我希望定义尽可能干净,以获得最大的可维护性(关于字符串键中的拼写错误),可读性和自动化。

我想到了如下的宏结构:

DEF_CLASS(MyClass)(
    DEF_FIELD(int, x)
    DEF_FIELD(bool, y)
    DEF_FIELD(float, z)
);

当我只想生成字段而不是构造函数时,我没有看到任何问题(反过来也是可能的,但我只会演示前者):

#define DEF_CLASS(CLASSNAME) \
    struct CLASSNAME { \
    _DEF_CLASS_TAIL/*..."curry the arguments"...*/
#define _DEF_CLASS_TAIL(FIELDS) \
        FIELDS \
    }
#define DEF_FIELD(TYPE, NAME) TYPE NAME;

我定义了第一个启动类定义的宏,并使用“curry”技术将第二个括号转发到第二个宏,该宏放置类定义的内容(FIELDS)并在之后关闭它。这样 - 我的想法也是如此 - 我可以访问第二个宏中的FIELDS

但是我现在如何输出两次字段,一个用于定义实际字段,另一个用于输出成员初始化?

我知道如果我以两种不同的方式定义宏DEF_CLASS_FIELD,然后稍微“包含”上面定义代码中的字段,每个宏定义一个,我可以一个接一个地正确打印它们。但由于字段列表是(并且应该)在类定义中,我不能简单地包含两次。

还有其他选择吗?

我尽量避免使用Boost预处理器库,但是如果你有一个很好的解决方案使用它,请继续。但是,我非常喜欢一个简单的解决方案,如果有的话。

1 个答案:

答案 0 :(得分:1)

这是一个实际上没有构建一个结构来缓存地图中的值的示例,但只是在构造函数中检查地图是否包含字段,如开头问题的评论中所述。

#define DEF_CLASS(CLASSNAME) \
    struct CLASSNAME { \
    CLASSNAME(QVariantMap& map) {\
    _DEF_CLASS_TAIL/*..."curry the arguments"...*/
#define _DEF_CLASS_TAIL(FIELDS) \
        FIELDS \
    }};
#define CHK_FIELD(TYPE, NAME) \
    if (typeid(TYPE)!=typeid(map[#NAME])) \
    { throw std::runtime_error(#NAME" missing or wrong type");}
DEF_CLASS(MyClass)(
    CHK_FIELD(int, x)
    CHK_FIELD(bool, y)
    CHK_FIELD(float, z)
);

这会从预处理器生成以下内容(在运行astyle之后):

struct MyClass {
   MyClass(QVariantMap& map) {
      if (typeid(int) != typeid(map["x"])) {
         throw std::runtime_error("x"" missing or wrong type");
      }
      if (typeid(bool) != typeid(map["y"])) {
         throw std::runtime_error("y"" missing or wrong type");
      }
      if (typeid(float) != typeid(map["z"])) {
         throw std::runtime_error("z"" missing or wrong type");
      }
   }
};

编辑:我不是100%确定typeid比较会像这样工作,但应该直截了当地用一个有效的支票替换它。