宏在 C 语言中非常重要,但在 C++ 中却无甚大用,普遍的共识:尽量避免使用宏

C++ 之父 Bjarne 在《C++ Programming Language》中写到

  • Avoid macros

《Effective C++》 条款 2

  • Prefer const, enum, and inline to #define

谷歌 C++ 编码规范,关于宏的描述

  • Avoid defining macros, especially in headers
  • Do not use macros to define pieces of a C++ API

1 禁用宏

谷歌 C++ 规范中,禁用宏的情况有三种:头文件、API 接口、程序文本

头文件中禁用宏,规范里写的很明确:

  • Don’t define macros in a.hfile.

对于 C++ API 接口,则是:

  • Do not use macros to define pieces of a C++ API

因此,如下形式的宏,是禁止的

class PANDA_TYPE(Foo) {  // ... public:  EXPAND_PUBLIC_PANDA_API(Foo)  EXPAND_PANDA_COMPARISONS(Foo, ==, <)};

程序文本中禁用宏,尤其是用 ## 来替换变量名

  • Don’t use macros for program text manipulation
  • Prefer not using##to generate function/class/variable names.

例如,下面代码是要避免的

#define CAT(a, b) a ## b#define STRINGIFY(a) #avoid f(int x, int y){    string CAT(x, y) = "asdf";   // BAD: hard for tools to handle (and ugly)    string sx2 = STRINGIFY(x);    // ...}  

2 替代宏

《Effective C++》 条款 2:用 const, enum 或 inline 来替代宏

用宏来表示常量和函数,是不推荐的

#define PI 3.14#define SQUARE(a, b) (a * b)

可用 constexpr 和 模板函数来替代,这样的好处:constexpr 定义的常量 kPI 会进入符号表,能被编译器识别到,编译报错时会提示 kPI 错误

而定义在 .h 中的宏,如果编译出错,只会提示 3.14 这个数值的错误,对于不是自己写的头文件,且常数含义未知时,很难查到错误来源

constexpr double kPI = 3.14;template 
T square(T a, T b) { return a * b; }  

同样,如下代码也是需要避免的

// webcolors.h (third party header)#define RED   0xFF0000#define BLUE  0x0000FF// productinfo.h, the following define product subtypes based on color#define RED    1#define BLUE   2int web = BLUE;   // web == 2; probably not what was desired

  可用 enum class 来代替,在 C++11 之 enum class 中也有提及

enum class Web_color { red = 0xFF0000, green = 0x00FF00, blue = 0x0000FF };enum class Product_info { red = 0, purple = 1, blue = 2 };int webby = blue;   // error: be specificWeb_color web = Web_color::blue;  

3 使用宏

虽然宏在 C++ 中如此被嫌弃,但为了兼容 C 语言,也不能直接将其删掉,这也是阻碍 C++ 发展的历史包袱

在某些方面,宏还是有点价值的,比如:头文件的保护宏

#ifndef FOO_BAR_BAZ_H_#define FOO_BAR_BAZ_H_...#endif  // FOO_BAR_BAZ_H_

还有一些预定义好的宏

__cpluplus__DATE____FILE____LINE__

在代码可读性上,宏往往会有意想不到的效果,如《The Art of Readable Code》中的例子

void AddStats(const Stats& add_from, Stats* add_to) {    add_to->set_total_memory(add_from.total_memory() + add_to->total_memory());    add_to->set_free_memory(add_from.free_memory() + add_to->free_memory());    add_to->set_swap_memory(add_from.swap_memory() + add_to->swap_memory());    add_to->set_status_string(add_from.status_string() + add_to->status_string());    add_to->set_num_processes(add_from.num_processes() + add_to->num_processes());    ...}

为了增强可读性,使用宏定义,可改为如下形式

void AddStats(const Stats& add_from, Stats* add_to){#define ADD_FIELD(field) add_to->set_##field(add_from.field() + add_to->field())    ADD_FIELD(total_memory);    ADD_FIELD(free_memory);    ADD_FIELD(swap_memory);    ADD_FIELD(status_string);    ADD_FIELD(num_processes);    ...#undef ADD_FIELD}

 当必须使用宏时,注意如下几点:

  • If you must use macros, use names with capital letters
  • Name macros with a project-specific prefix
  • #definemacros right before you use them, and#undefthem right after.

参考资料

C++ Core GuideLines

谷歌 C++ 编码规范 – Preprocessor Macros

《The Art of Readable Code》 chapter 8

后记

写完博文,当我还沉浸在搞清一个 C++ 知识点的兴奋中时,突然想到鲁迅笔下的《孔乙己》,这篇博文,不就是教茴字四种写法的现代版么?

孔乙己的悲剧,更多是因时代巨变所致,是旧社会一代读书人的命运缩影,如果时代没有变,兴许茴字的写法,也是科举考试中的一个知识点。

然而,孔乙己还是有一技之长的,”幸而写得一笔好字,便替人家抄抄书,换一碗饭吃”。在如今经济停滞甚至衰退的浪潮下,我又有什么一技之长 “换一碗饭吃” 呢?

写到此,我也没有答案,孔乙己 = 恐怕以为是自己,只能以《孔乙己》的结尾警示自己:我到现在终于没有见——大约孔乙己的确失业了…

原文链接: http://www.cnblogs.com/xinxue/

专注于机器视觉、OpenCV、C++ 编程