[关闭]
@env0y 2015-08-21T23:32:41.000000Z 字数 1757 阅读 1271

X-Macro

30年前我念大学时从一个朋友那里学来的一个技巧。它是汇编语言的一个宏,但很容易转换为C语言宏。我一直在使用它,但有意思的是我还从没在别人的代码中看到过。现在该我把这个小技巧传递下去了。

让我们举个陈腐的栗子。假设我们有一个头文件叫color.h,里面有一个颜色的宏:

enum Color { Cred, Cblue, Cgreen };

在相应的源文件color.c中,为了正确的打印颜色,有一个字符串数组:

static char *ColorStrings[] = {"red", "blue", "green"};

我们可以这样使用:

enum Color c;
...
printf("the color is %s\n",
ColorStrings[c]);

到目前为止一切都很好。随着时间推移,假如新加入一个颜色:

enum Color{ Cred, Cyellow, Cblue, Cgreen };`

是的,假如我们忘记更新数组ColorStrings[]了,打印Cyellow却输出了“blue”,更糟糕的是,如果打印Cgreen会造成数组越界。(作为一个聪明的程序员,你是不可能犯这样的错误的,对么?)

主要问题是在enum和数组之间没有语义连接。 通常的解决办法是添加一个单元测试包。但如果我们能找到一个连接enum和数组的方法,从而在编译时检测到此类错误,岂不美哉?

--- X 宏---
它能做到这一点么?

#define COLORS \
    X(Cred, "red") \
    X(Cblue, "blue") \
    X(Cgreen, "green")

把这个放在color.h中。接下来的是颜色枚举的定义:

#define X(a, b) a,
    enum Color { COLORS };
#undef X

在源代码文件color.c中这样定义数组:

#define X(a, b) b,
    static char *ColorStrings[] = { COLORS };
#undef X

可以看出,我们重新定义了X宏,以便提取出必要的信息而忽略其它。正确的宏管理在这里得以体现,因为如果X已经定义过#define X将会抱怨,而#undef保证了这一点不会发生。

现在如果再添加一个颜色将变得非常简单:

#define COLORS \
    X(Cred, "red") \
    X(Cyellow, "yellow") \
    X(Cblue, "blue") \
    X(Cgreen, "green")

enum和数组都自动得到了更新,看起来很美妙是不是。有经验的程序员会立刻明白可以有更复杂的设计:

#define COLORS \
    X(red) \
    X(blue) \
    X(green)

#define X(a) C##a,
    enum Color { COLORS };
#undef X

#define X(a) #a,
    static char *ColorStrings[] = { COLORS };
#undef X

一个真实的例子是在C++编译器 Digital Mars前端:

#define ENUMSCMAC       \
    X(unde, SCEXP|SCKEP|SCSCT  ) \
    X(auto, SCEXP|SCSS|SCRD    ) \
    X(static, SCEXP|SCKEP|SCSCT) \
    X(thread, SCEXP|SCKEP      ) \
    ...

3个独立但并行构造的构建 - 枚举,用于打印的字符串表,以及数组。

我使用过的最复杂的X宏有6个参数,它可以构造枚举,结构初始化,运行时初始化等。

当然,你可能已经在使用一个叫X的宏或者变量,且在宏内部X是硬编码的。 Andrei Alexandrescu(Author of Modern C++ Design)建议以下改进,即将X宏作为参数:

#define FOR_ALL_COLORS(apply) \
apply(red) \
apply(blue) \
apply(green)

紧接着:

#define SELECT_STRING(a) #a,
static char *ColorStrings[] =
{ 
    FOR_ALL_COLORS(SELECT_STRING)
};
#undef SELECT_STRING 

任何语言只要支持文本宏预处理程序,X宏技术就能大展身手。C语言肯定能胜任工作。使用并且传播它,就像我贴心的朋友把他告诉我一样。:)

(我)

就像之前说的那样,我还从来没看见过其他人使用这个技巧。因为它晦涩难懂么?欢迎评论。

原文

扩展1

扩展2

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注