Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

D.R.Y vs "avoid macros"

Tags:

c++

dry

I am creating my own implementation of XUL in C++ using the Windows API. The fact that the elements are constructed by the XML parser requires that they have identical interfaces, so that we don't need to write custom code for each element constructor. The result is that most of my elements look like this:

class Button : public Element
{
public:
    static const char * Type() { return "button"; }

private:
    friend class Element;
    Button(Element * inParent, const AttributesMapping & inAttributesMapping);
};


class Label : public Element
{
public:
    static const char * Type() { return "label"; }

private:
    friend class Element;
    Label(Element * inParent, const AttributesMapping & inAttributesMapping);
};


class Description : public Element
{
public:
    static const char * Type() { return "description"; }

    virtual bool init();

private:
    friend class Element;
    Description(Element * inParent, const AttributesMapping & inAttributesMapping);
};

So there is a lot of code duplication here. I wonder if it would be a good idea to replace them with macro calls like this:

#define DECLARE_ELEMENT(ElementType, XULName)           \
class ElementType : public Element                      \
{                                                       \
public:                                                 \
    static const char * Type() { return XULName; }      \
                                                        \
private:                                                \
    friend class Element;                               \
    ElementType(                                        \
        Element * inParent,                             \
        const AttributesMapping & inAttributesMapping); \
};                                                      \


DECLARE_ELEMENT(Window, "window")
DECLARE_ELEMENT(Button, "button")
DECLARE_ELEMENT(Label, "label")

I haven't completely worked out the concept yet, so a few things are missing here, like the class definitions, and (maybe) the ability to add methods per element.

But I'd like to know your opinion of using macros in this situation. Feel free to share your thoughts.

EDIT

I am now using a small ruby script that generates the source and header files from a set of templates. I enhanced the scripts so that the files are also automatically marked for addition on SVN, and the Visual Studio project file is modified to include the files. This saves me a lot of manual labor. I'm quite happy with this solution. FYI this is what the templates look like now:

#ifndef {{ELEMENT_NAME_UPPER}}_H_INCLUDED
#define {{ELEMENT_NAME_UPPER}}_H_INCLUDED


#include "XULWin/Element.h"


namespace XULWin
{

    class {{ELEMENT_NAME}} : public Element
    {
    public:
        static ElementPtr Create(Element * inParent, const AttributesMapping & inAttr)
        { return Element::Create<{{ELEMENT_NAME}}>(inParent, inAttr); }

        static const char * Type() { return "{{ELEMENT_TYPE}}"; }

        virtual bool init();

    private:
        friend class Element;
        {{ELEMENT_NAME}}(Element * inParent, const AttributesMapping & inAttributesMapping);
    };

} // namespace XULWin


#endif // {{ELEMENT_NAME_UPPER}}_H_INCLUDED

CPP document:

#include "XULWin/{{ELEMENT_NAME}}.h"
#include "XULWin/{{ELEMENT_NAME}}Impl.h"
#include "XULWin/AttributeController.h"
#include "XULWin/Decorator.h"


namespace XULWin
{

    {{ELEMENT_NAME}}::{{ELEMENT_NAME}}(Element * inParent, const AttributesMapping & inAttributesMapping) :
        Element({{ELEMENT_NAME}}::Type(),
                inParent,
                new {{ELEMENT_NAME}}Impl(inParent->impl(), inAttributesMapping))
    {
    }


    bool {{ELEMENT_NAME}}::init()
    {
        return Element::init();
    }

} // namespace XULWin
like image 989
StackedCrooked Avatar asked Oct 12 '09 14:10

StackedCrooked


3 Answers

If you use a template solution, you can avoid macros and avoid repeating yourself:

template <const char *XULName>
class ElementType : public Element
{
public:
    static const char * Type() { return XULName; }

private:
    friend class Element;
    ElementType(
        Element * inParent,
        const AttributesMapping & inAttributesMapping);
};

char windowStr[]="window";
char buttonStr[]="button";
char labelStr[]="label";

typedef ElementType<windowStr> Window;
typedef ElementType<buttonStr> Button;
typedef ElementType<labelStr> Label;

Rule of thumb: Templates can be used for just about everything that macros were necessary for in C.

Implementation note: String literals can't be used directly as template arguments because they have internal linkage -- that's why you need the windowStr etc. In practice, you would want to put the declarations of windowStr, buttonStr and labelStr in the H file and the definitions of those strings in a CPP file.

like image 85
Martin B Avatar answered Sep 23 '22 09:09

Martin B


I would not use a macro here. The clue is in your class "Description", which has an extra member function init, which the others don't. So you wouldn't be able to use the macro to define it, but you'd instead expand the macro manually and add the extra line.

To me, this is a bigger violation of DRY than just writing out all the class definitions. Almost not repeating yourself, but doing it just for one case, often ends up harder to maintain that repeating yourself consistently. DRY is about finding good abstractions, not just cutting down on boilerplate.

I might replace those constructors, though, with a SetAttributes function in class Element. That might cut the amount of boilerplate actually required in each derived class, since constructors are the one thing that can't be inherited from the base. But it depends how similar the implementations are of the constructor of each class.

like image 32
Steve Jessop Avatar answered Sep 23 '22 09:09

Steve Jessop


As an alternative, you might consider generating the code an a separate build step, instead of using the preprocessor. I like cog, But you could use whatever you like -- This way you get full programmatic control over what is generated. (Macros are powerful, but limited in what you can do.)

like image 35
Sean McMillan Avatar answered Sep 25 '22 09:09

Sean McMillan