Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ alternatives to preprocessor macro code generation?

Tags:

c++

I have a collection of about 50 small, very similary structured classes that all derive from a common base. The classes represent items that are read in from a file as pairs of strings, where the first string is used to identify the type of the pair (which derived class should be used to represent the data) and the second is the data itself. There is also a visitor (as in visitor pattern) class associated with the derived classes and a factory class for generating the appropriate derived class from the type identification string.

The setup looks something like this:

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

Since this code is very repetitive, takes up a lot of space, and since adding a new item type would require remembering to add lines in several places, I am using macros to create the derived classes and to add :

#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

Now for the question: is using macros to "compact" this code the "right" way to do this, or is there a more elegant/cleaner approach? Comments suggesting an alternative design are also welcome: I'm still pretty new to object-oriented programming and don't have a good feel for what's "right" yet.

Thank you very much in advance!

like image 840
Anonymous Avatar asked Sep 15 '25 03:09

Anonymous


2 Answers

You may want to check out a copy of "Modern C++ Design," by Andrei Alexandrescu, which shows how to automatically generate most of this code using the C++ template system. Alexandrescu dedicates two chapters to the visitor pattern and automatically generating class hierarchies, which seems exactly like what you're looking for. I won't try to replicate the code in this answer, primarily because it's really dense, I'd probably get it wrong, and the book has a much better explanation. :-)

like image 180
templatetypedef Avatar answered Sep 16 '25 18:09

templatetypedef


Perhaps there is a reason for all the inheritance, but it does seem rather like a lot of code.

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

Generally, I wouldn't expect to need internals of the type to create a visitor. I suspect your use for that is a violation of open/closed principal. I'd recommend getting familiar with boost::variant and boost::static_visitor to see how they do it. Maybe I'm being a bit presumptuous, not sure.

like image 26
Tom Kerr Avatar answered Sep 16 '25 19:09

Tom Kerr