C++ 之 宏定义
阅读原文时间:2023年07月08日阅读:1

宏在 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 来代替,在 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 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++ 知识点的兴奋中时,突然想到鲁迅笔下的《孔乙己》,这篇博文,不就是教茴字四种写法的现代版么?

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

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

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