【问题标题】:Code generation with macros: Class with members and constructor使用宏生成代码:具有成员和构造函数的类
【发布时间】:2013-05-03 03:25:59
【问题描述】:

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

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

如您所见,我有一个 QVariantMap(类似于std::map<std::string, boost::variant<...>>,对于那些不熟悉 Qt 的人),我希望能够在不进一步了解其字段的情况下构造这样的类型,因此方便的构造函数,它应该“反序列化”地图中的字段。

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

我想到了如下的宏观结构:

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 预处理器库,但如果您有一个很好的解决方案可以使用它,请继续。不过,如果有的话,我更喜欢简单的解决方案。

【问题讨论】:

  • 这不是您想要的解决方案,但最简单的当然是在结构中保留指向 QVariantMap 的引用/指针。由于您的所有结构都会不同(或者您不需要宏),因此您可能必须在某个地方编写专门针对每个特定结构的代码。在该代码中,您可以缓存结构持有的 QVariantMap 中的值,或者每次只使用映射查找。
  • 是的,实际上我当前的临时解决方案正是......我将变量映射存储在类中,并让宏为每个“字段”生成一个 getter,并为每个“字段”提供访问(和转换)称呼。我对这种方法的主要问题不是性能,而是地图是否真的包含预期字段的“后期检查”。如果数据格式错误,我想在构造函数中调用一些错误处理程序(或抛出异常)...
  • 好吧,如果您每次都可以进行地图查找,那么就不需要缓存这些值。在这种情况下,代替 DEF_FIELD,也许你可以只创建一个宏,它接受一个映射键(你可以使用#x 来生成"x")和一个类型,然后执行检查:if (typeid(map[KEY])!=typeid(TYPE)) { throw; }?跨度>

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


【解决方案1】:

这是一个示例,它实际上并没有构建一个结构来缓存映射中的值,而只是在构造函数中检查映射是否包含字段,如开题的 cmets 中所述。

#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 比较会像这样工作,但应该直接用有效的检查替换它。

【讨论】:

  • 非常感谢。 QVariant 使用.canConvert<T>(),然后使用.value<T>(),但正如您所说:调整它很简单。但是:如果我不将数据作为字段提供,我希望将它们作为吸气剂,并且您的解决方案没有两者中的任何一个(因为您在检查上“花费”了宏扩展)。也许我可以通过包含定义两次,首先生成检查器,然后是在其构造函数中使用检查器的实际类,将两个类定义(您的“检查器”和我当前的解决方法与 getter 但没有检查)结合起来。
  • (续)问题中讨论的多个包含多个定义的问题主要适用于我必须将两个不同的东西放在一个类的情况(比如字段和构造函数实现),如果不是不可能的话,我预计这将非常困难。
  • 这可以工作..或者您可以更改DEF_CLASS,使其具有struct CLASSNAME { QVariantMap& fMap; CLASSNAME(QVariantMap& map) : fMap(map) {\。现在进行检查的 ctor 还存储了对地图的引用,因此稍后在代码中当您需要 getter 时,您只需执行 objOfMyClass.fMap["x"] - 因为我仍然假设当您想要使用地图中的值,您必须具体并输入它。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-02-10
  • 1970-01-01
  • 2015-05-25
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多