假设我要定义以下结构的类:
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预处理器库,但是如果你有一个很好的解决方案使用它,请继续。但是,我非常喜欢一个简单的解决方案,如果有的话。
答案 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比较会像这样工作,但应该直截了当地用一个有效的支票替换它。