我使用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
答案 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文件中的{},windowStr
和buttonStr
以及CPP文件中这些字符串的定义。
答案 1 :(得分:5)
我不会在这里使用宏。线索在你的“描述”类中,它有一个额外的成员函数init
,其他人没有。因此,您将无法使用宏来定义它,但您可以手动扩展宏并添加额外的行。
对我来说,这是对DRY的更大违反,而不仅仅是写出所有的类定义。 几乎不会重复自己,但只针对一个案例,通常最终会更难以维持一直重复自己。 DRY是关于找到好的抽象,而不仅仅是减少样板。
我可以用类SetAttributes
中的Element
函数替换那些构造函数。这可能会减少每个派生类实际需要的样板量,因为构造函数是不能从基类继承的东西。但这取决于每个类的构造函数的实现有多相似。
答案 2 :(得分:5)
作为替代方案,您可以考虑将代码生成为单独的构建步骤,而不是使用预处理器。我喜欢cog,但你可以使用你喜欢的任何东西 - 这样你就可以完全控制生成的内容。 (宏很强大,但你可以做的事情有限。)
答案 3 :(得分:4)
我认为宏可以在这样的低水平上减少重复(因此引入错误的风险)。
宏的使用将保持非常本地化,并且应该使代码整体更容易理解。当然,它也可能需要一些文档工作。
答案 4 :(得分:3)
使用任何使代码更简单的东西。
DRY和Avoid Macro都有相同的目标:让您的代码更简单。
与往常一样,我建议遵循精神而非信件。在你的情况下,很明显宏实际上会简化你的代码,所以你应该使用它。
但是,考虑到宏可能引入的问题,请确保将其命名为“安全”。例如,在开头包含项目名称/文件名,以减少与现有宏的潜在“冲突”。
(你可以看看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
类结构答案 11 :(得分:0)
这段代码看起来非常像Tom Cargill在他的书“C++ Programming Style”的第1章中分析和重新组装的程序,可追溯到1992年。不可否认,该代码没有使用宏来复制几乎相同的类,但最终结果看起来非常相似。