C++ 之 宏定义
宏在 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
.h
file.
对于 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) #a void 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 2 int web = BLUE; // web == 2; probably not what was desired
可用 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 specific Web_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
#define
macros right before you use them, and#undef
them right after.
参考资料
C++ Core GuideLines
谷歌 C++ 编码规范 - Preprocessor Macros
《The Art of Readable Code》 chapter 8
后记
写完博文,当我还沉浸在搞清一个 C++ 知识点的兴奋中时,突然想到鲁迅笔下的《孔乙己》,这篇博文,不就是教茴字四种写法的现代版么?
孔乙己的悲剧,更多是因时代巨变所致,是旧社会一代读书人的命运缩影,如果时代没有变,兴许茴字的写法,也是科举考试中的一个知识点。
然而,孔乙己还是有一技之长的,"幸而写得一笔好字,便替人家抄抄书,换一碗饭吃"。在如今经济停滞甚至衰退的浪潮下,我又有什么一技之长 "换一碗饭吃" 呢?
写到此,我也没有答案,孔乙己 = 恐怕以为是自己,只能以《孔乙己》的结尾警示自己:我到现在终于没有见——大约孔乙己的确失业了...