D.R.Y vs“避免宏”

时间:2009-10-12 14:28:26

标签: c++ dry

我使用Windows API在C ++中创建自己的XUL实现。元素由XML解析器构造的事实要求它们具有相同的接口,因此我们不需要为每个元素构造函数编写自定义代码。结果是我的大部分元素都是这样的:

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

所以这里有很多代码重复。我想知道用这样的宏调用替换它们是否是个好主意:

#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")

我还没有完全理解这个概念,所以这里缺少一些东西,比如类定义,以及(可能)每个元素添加方法的能力。

但是我想知道你在这种情况下使用宏的意见。随意分享您的想法。

修改

我现在正在使用一个小的ruby脚本,它从一组模板生成源文件和头文件。我增强了脚本,以便在SVN上自动标记文件以便添加,并修改Visual Studio项目文件以包含文件。这为我节省了大量的手工劳动。我很满意这个解决方案。仅供参考,这就是模板现在的样子:

#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文件:

#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

12 个答案:

答案 0 :(得分:39)

如果您使用模板解决方案,则可以避免宏避免重复自己:

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;

经验法则:模板几乎可以用于C中所需的所有宏。

实现说明:字符串文字不能直接用作模板参数,因为它们具有内部链接 - 这就是您需要windowStr等的原因。实际上,您需要放置{{1}的声明H文件中的{},windowStrbuttonStr以及CPP文件中这些字符串的定义。

答案 1 :(得分:5)

我不会在这里使用宏。线索在你的“描述”类中,它有一个额外的成员函数init,其他人没有。因此,您将无法使用宏来定义它,但您可以手动扩展宏并添加额外的行。

对我来说,这是对DRY的更大违反,而不仅仅是写出所有的类定义。 几乎不会重复自己,但只针对一个案例,通常最终会更难以维持一直重复自己。 DRY是关于找到好的抽象,而不仅仅是减少样板。

我可以用类SetAttributes中的Element函数替换那些构造函数。这可能会减少每个派生类实际需要的样板量,因为构造函数是不能从基类继承的东西。但这取决于每个类的构造函数的实现有多相似。

答案 2 :(得分:5)

作为替代方案,您可以考虑将代码生成为单独的构建步骤,而不是使用预处理器。我喜欢cog,但你可以使用你喜欢的任何东西 - 这样你就可以完全控制生成的内容。 (宏很强大,但你可以做的事情有限。)

答案 3 :(得分:4)

我认为宏可以在这样的低水平上减少重复(因此引入错误的风险)。

宏的使用将保持非常本地化,并且应该使代码整体更容易理解。当然,它也可能需要一些文档工作。

答案 4 :(得分:3)

使用任何使代码更简单的东西。

DRY和Avoid Macro都有相同的目标:让您的代码更简单。

  • 干:避免重复
  • 避免宏:因为它们可能会引入难以诊断编译器错误或难以诊断错误(因为它们绕过命名空间边界而不是C ++感知/类型安全)。

与往常一样,我建议遵循精神而非信件。在你的情况下,很明显宏实际上会简化你的代码,所以你应该使用它。

但是,考虑到宏可能引入的问题,请确保将其命名为“安全”。例如,在开头包含项目名称/文件名,以减少与现有宏的潜在“冲突”。

(你可以看看BOOST标题保护,以了解命名约定)

答案 5 :(得分:3)

如果您计划使用自动代码文档工具(如doxygen),请谨慎使用替换class定义的宏。在生成任何文档之前,您必须通过预处理器运行代码。也许,这不是最重要的考虑因素,但仍需要考虑。

答案 6 :(得分:2)

恕我直言这个宏是有道理的。虽然我认为最好添加#undef DECLARE_ELEMENT以防止悬空宏。 (除非您打算在其他文件中使用此宏。)

但请注意,只有当这些类永远不会有太大差异(或最好)时,这才有效。


还有另一种使用模板的解决方案。请考虑以下代码

namespace impl
{
    struct ButtonTag;
    struct LabelTag;


    template< typename TypeTag >
    struct NameGenerator;

    template<>
    struct NameGenerator< ButtonTag >
    {
        static const char * getName() { return "button"; }
    };

    template<>
    struct NameGenerator< LabelTag >
    {
        static const char * getName() { return "label"; }
    };


    template< typename TypeTag >
    class SimpleElement : public Element
    {
    public:
        static const char * Type()
        { return NameGenerator< TagType >::getName(); }

    private:
        friend class Element;

        SimpleElement(
            Element * inParent,
            const AttributesMapping & inAttributesMapping);

    };
}

typedef impl::SimpleElement< impl::ButtonTag > Button;
typedef impl::SimpleElement< impl::LabelTag > Label;

虽然避免了宏,但它有点冗长。

答案 7 :(得分:1)

代码示例

enum Types { BUTTON, LABEL,...}

struct TypeList {
    static const char * Type(const int nID)
    {
         switch(nID) {
         case BUTTON: return "button";
         ...
    }
};

template<ID>
class IElem : public Element
{
private:
    static TypeList m_oTypeList;

public:
    static const char * Type() { return m_oTypeList.Type(ID); }
private:
friend class Element;
    IElem(Element * inParent, const AttributesMapping & inAttributesMapping)
    {...}
};

用于非常用功能和专用

class Button : public IElem<BUTTON>
{
...
}

答案 8 :(得分:1)

在使用宏时,我甚至可以更进一步使用单哈希和双哈希功能。单个哈希创建字符串常量和双连接标识符以构建新的组合。

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

DECLARE_ELEMENT(window); // defines Cwindow
DECLARE_ELEMENT(button); // defines Cbutton
DECLARE_ELEMENT(label);  // defines Clabel

例如,以下代码是我有时会编写的,用于测试某些常见类型的sizeof。

#include <stdio.h>

#define OUT( _type ) printf("sizeof(%s) = %d\n", #_type, sizeof(_type))

int main() {
  OUT( char );
  OUT( int );
  OUT( short );
  OUT( long );
  OUT( long long );
  OUT( void* );
  return 0;
}

答案 9 :(得分:0)

在这种情况下,我会投票支持宏。毕竟它们并不是那么糟糕,你不应该尝试用它们来编写内联函数,而不是它们是好的。

答案 10 :(得分:0)

我认为在这种情况下使用宏是可以的,但仅限于

  • 可以开发一个不太复杂的解决方案,但涵盖(最好)所有必要的Element类结构
  • 记录宏井
  • 意识到某些IDE在宏生成的类结构方面存在问题,并且可能会产生后果

答案 11 :(得分:0)

这段代码看起来非常像Tom Cargill在他的书“C++ Programming Style”的第1章中分析和重新组装的程序,可追溯到1992年。不可否认,该代码没有使用宏来复制几乎相同的类,但最终结果看起来非常相似。