以const代替#define定义常量
Effective C++
中给出的遵循这个条款大概理由如下:
#define的宏在预处理阶段就已经被替换掉,所以编译器可能看不到宏的存在,只看到被替换的内容,从而导致因这个宏的错误使用而导致的编译错误没有被正确得提示。
#define ASPECT_RATIO 1.653
例如和上面这段宏有关的编译错误,可能会提及1.653而不是ASPECT_RATIO
。
但是随着编译器的发展,一个健壮的编译器已经能发现并定位到#define导致的错误。
例如这段代码:
#define ASPECT_RATIO 1.653
int main() {
ASPECT_RATIO = 1;
}
这段代码存在试图给宏ASPECT_RATIO
赋值,因为ASPECT_RATIO
是一个左值自然会出现编译错误。
replace_define.cpp: In function ‘int main()’:
replace_define.cpp:4:20: error: lvalue required as left operand of assignment
4 | ASPECT_RATIO = 1;
|
这段error
的含义是:赋值操作的左侧需要一个lvalue
,可以看到编译器正确检查出错误类型并提示发生错误的位置和具体内容。
虽然编译器能正确检查出错误并正确提示,但是还是建议使用const提到#define。#define给我们很高的自由度并且它没有类型检查,这会导致我们在定义了错误类型的宏但不能及时发现,const提供类型检查,可以配合LSP在编写代码阶段就发现问题。
例如我们想定义一个字符串,但是手误写成了字符:
#define SOME_STRING '1'
这并不会出现错误提示,因为LSP
也不知道你想定义的是什么类型,但是使用cons
t就会存在提示:
const std::string someString = '1';
no suitable constructor exists to convert from "char" to "std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>"C/C++(415)
使用inline代替#define定义类似函数的宏
有时候我们会定义一个类似函数的宏,这种宏的替代可以减少函数调用的开销,但是这种宏在使用的过程中,也是误用的重灾区。
考虑如下情况:
定义一个计算平方的宏:
#define SQUARE(x) x * x
使用该宏:
int y = SQUARE(3);
你得到了你期望的值9
,你又测试了很多组数据,从负数到零再到更多正数
,都没有问题,于是你认为这段代码没有任何问题,殊不知你已经埋下了一颗随时会爆炸的地雷。
因为只有当你以下面这种方式使用时,它才会显出原形:
int y = SQUARE(1 + 2)
输出:
5
之所以会出现这么诡异的情况,是因为#define
只是简单的文本替换,所以int y = SQUARE(1 + 2)
实际上被替换成了int y = 1 + 2 * 2 + 1
,y
自然就得到了5
。
要解决这个问题,需要给x(变量)
裹上()
。
#define SQUARE(x) ((x) * (x))
宏SQUARE
加上()
确实是解决的,但是()
却没能解决所有问题。
#include <iostream>
#define MAX(a, b) ((a) > (b) ? (a) : (b))
int main() {
int a = 5, b = 0; // a = 5, b = 0
MAX(++a, ++b); // a = 7, b = 1, ++a执行了两次,++b执行一次
MAX(++a, ++b + 10); // a = 8, b = 3, ++a执行一次,++b执行两次
// ...
}
这里的a
和b
分别被错误得进行两次++
操作,真得让人头大。
与其花心思琢磨它会有什么样的魔术效果,不如考虑使用更稳定的inline
。inline
会将函数调用替换为函数本身,从而减少函数调用的开销。最重要的是它可以享受函数的可预见性和类型安全,不会像宏一样产生魔术效果。
考虑将上面那段代码替换为:
#include <iostream>
template <typename T>
inline T MAX(const T& a, const T& b) {
return a > b ? a : b;
}
int main () {
int a = 5, b = 0; // a = 5, b = 0
MAX(++a, ++b); // a = 6, b = 1, 都正确地只执行1次++。
MAX(++a, ++b + 10); // a = 7, b = 2, 都正确地只执行1次++。
// ...
}
这段代码就不会出现错误得进行两次++
操作的行为。
总而言之,我们应该减少对#define
的依赖,使用const
替代#define
定义常量,使用inline
替代#define
定义类似函数的宏。