【问题标题】:C++ alternatives to preprocessor macro code generation?预处理器宏代码生成的 C++ 替代方案?
【发布时间】:2011-12-09 23:54:26
【问题描述】:

我收集了大约 50 个非常相似的小型结构化类 都源于一个共同的基础。类代表读入的项目 从文件中作为字符串对,其中第一个字符串用于标识 对的类型(应该使用哪个派生类来表示数据)和 第二个是数据本身。还有一个访问者(如访问者模式) 与派生类关联的类和用于生成的工厂类 来自类型标识字符串的适当派生类。

设置如下所示:

class NodeItemVisitor;  // Forward declaration.

class NodeItemBase
{
    public:
      std::string get_val() const { return val; }
      virtual std::string idstr() const = 0;
      virtual void accept(NodeItemVisitor& v) = 0;

    private:
      std::string val;
};

// Forward declarations of derived classes.
class NodeItemA;
class NodeItemB;
...
class NodeItemZ;

class NodeItemVisitor
{
    public:
        virtual void visit(NodeItemA& ni) = 0;
        ...
        virtual void visit(NodeItemZ& ni) = 0;
};

class NodeItemA : public NodeItemBase
{
    public:
        virtual std::string idstr() const { return "A"; }
        virtual void accept(NodeItemVisitor& v) { v.visit(*this); return; }
};

...

class NodeItemZ : public NodeItemBase
{
    public:
        virtual std::string idstr() const { return "Z"; }
        virtual void accept(NodeItemVisitor& v) { v.visit(*this); return; }
};

class NodeItemFactory
{
    public:
        // Uses a lookup table to map the input string to one of the "mkni" 
        // functions below and then calls it.
        static NodeItemBase* mknifromid(const std::string& id);

    private:
        static NodeItemBase* mkniA(void) { return new NodeItemA(); }
        ...
        static NodeItemBase* mkniZ(void) { return new NodeItemZ(); }
};

由于这段代码非常重复,占用大量空间,并且由于添加了一个 新的项目类型需要记住在几个地方添加行,我是 使用宏创建派生类并添加:

#define ADD_NODE_ITEMS \
    ADD_NODE_ITEM(A); \
    ...
    ADD_NODE_ITEM(Z);

#define ADD_NODE_ITEM(ID) \
class NodeItem##ID : public NodeItemBase \
{ \
    public: \
        virtual std::string idstr() const { return #ID; } \
        virtual void accept(NodeItemVisitor& v) { v.visit(*this); return; } \
}

ADD_NODE_ITEMS
#undef ADD_NODE_ITEM

class NodeItemVisitor
{
    public:
#define ADD_NODE_ITEM(ID) \
    virtual void visit(NodeItem##ID& ni) = 0;
    ADD_NODE_ITEMS
#undef ADD_NODE_ITEM
};

class NodeItemFactory
{
    public:
        // Uses a lookup table to map the input string to one of the "mkni" 
        // functions below and then calls it.
        static NodeItemBase* mknifromid(const std::string& id);

    private:
#define ADD_NODE_ITEM(ID) \
    static NodeItemBase* mkni##ID(void) { return new NodeItem##ID(); }
    ADD_NODE_ITEMS
#undef ADD_NODE_ITEM
};

#undef ADD_NODE_ITEMS

现在的问题是:使用宏来“压缩”这段代码是“正确”的方式 要做到这一点,还是有更优雅/更清洁的方法?评论建议 也欢迎替代设计:我还是很陌生 面向对象的编程,对什么是“正确的”还没有很好的感觉。

非常感谢您!

【问题讨论】:

  • 为什么需要这么多派生类?它们有何不同?

标签: c++


【解决方案1】:

您可能想查看 Andrei Alexandrescu 撰写的“现代 C++ 设计”的副本,其中展示了如何使用 C++ 模板系统自动生成大部分代码。 Alexandrescu 有两章专门介绍访问者模式并自动生成类层次结构,这看起来与您正在寻找的完全一样。我不会尝试复制这个答案中的代码,主要是因为它真的很密集,我可能会弄错,而且这本书有更好的解释。 :-)

【讨论】:

    【解决方案2】:

    也许所有的继承都是有原因的,但它确实看起来有点像很多代码。

    template<typename T>
      struct class_trait;
    
    #define QUOTE(X) #X
    
    #define CLASS_TRAIT(NAME) \
      template<> struct class_trait<NAME> { \
        static std::string class_string() {return QUOTE(NAME);} \
      }
    
    template<typename T>
      std::string GetClassString() {
        return class_trait<T>::class_string();
      }
    

    一般来说,我不希望需要该类型的内部结构来创建访问者。我怀疑您对此的使用违反了开放/封闭原则。我建议熟悉 boost::variant 和 boost::static_visitor 以了解它们是如何做到的。也许我有点冒昧,不确定。

    【讨论】:

      【解决方案3】:

      我会推出一个小型生成器脚本,并且永远不会手动接触生成的源代码。有明确的代码(没有宏)也总是更适合调试。

      【讨论】:

        【解决方案4】:

        我会努力开发代码生成器/模板语言。我已经专业地结合了 Nvelocity 模板引擎,它是非常简单和好的语言(但不是一个很好的解析器!)和 C# 的组合,结果非常好。

        我不知道你是否使用 Visual Studio 2010,但我听说它现在有一个名为 T4 的模板引擎。我从来没有尝试过,所以我不是谈论它的理想人选,但如果我在你的位置,我会按照这个方向进行调查。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2018-01-03
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-11-21
          • 2014-02-01
          相关资源
          最近更新 更多