条款02 :尽量以 const, enum, incline 替换 #define
"宁可以编译器替换预备处理器"。
常量用途
#define (宏 macro)定义常量时,名称不为编译器所识别,只识别值,因而报错不会提及其名,只告诉你有这么一个值。编译遇到一次应用,就产一次副本。它不共享常量,容易浪费内存。同时 #define 也不能提供封装性。如果改用常量,那么编译器就能够正确识别其名。
不过改用常量会带来两种情况
- 定义常量指针。由于常量定义式通常放在头文件内,最好把指针本身声明为const。
- 类内常量。作为类成员的常量可以限制其作用域。此外,使用static定义常量可以保证所有这个类的实例(继承也同样)共享这一个常量,从而节省性能。顺带一提,constexpr也可以达成类似的效果。
不过这还带来一个问题。按书上所言,在类内我们声明 int a[c]; 这里的c为static const常量的话,其作为常量声明式,而数组需要一个定义式。在visual studio 2022中,可能是编译器优化的缘故,我没能成功复现。如果存在错误,我们需要在类外进行初始化如 const int MyClass::c; 而由于它声明时已经获得一个值,则不可再设初始值,或者可以干脆把初始值移到这边定义。
The enum hack则是直接在类内 enum {c = 5}; 除了可以实现上述行为,enum还能类似#define那样不能被取地址。
形似函数宏用途
而对于#define实现宏,宏不会招致函数调用而带来额外开销。具体而言,宏是静态展开的,运行时不存在压栈、跳转、返回等函数调用操作。不过要是多次调用宏,可能会增加二进制文件大小(对incline而言,内联会增大代码体积,但减少调用开销)。
书中提到一个形似函数的宏 #define CALL_WITH_MAX(a, b) f( (a) > (b) ? (a) : (b) )
对于CALL_WITH_MAX(++a,b),如果在a>b的情况下,其在调用时与选中a时都会自增一次,而a<b情况则只有调用时一次。
对此提出了template incline方法,可以免去括号,作为真正的函数,也享有减少开销的福利。template<T>
incline void callWithMax(const T&a, const T&b)
{
f(a>b?a:b);
}
Comments NOTHING