Item 2: 尽量使用 const、enum、inline等替换 #define
这个 Item 改名为 用 编译器 取代 预处理器 也许更好一些,因为 #define 根本就没有被看作是语言本身的一部分。
const 常量代替 #define
#define ASPECT_RATIO 1.653
编译器也许根本就没有看见这个符号名 ASPECT_RATIO,在编译器得到源代码之前,这个名字就已经被预处理器消除了。结果,名字 ASPECT_RATIO 可能就没有被加入符号表。为后期的调试带来极大的麻烦。
使用 const 常量代替 #define:
//@ uppercase names are usually for macros, hence the name change
const double AspectRatio = 1.653;
作为一个 语言层面上的常量 AspectRatio 被编译器明确识别并确实加入符号表。另外,对于浮点常量来说,使用 常量比使用 #define 能产生更小的代码。这是因为预处理器盲目地用 1.653 置换宏名字 ASPECT_RATIO,导致你的目标代码中存在多个 1.653 的拷贝,如果使用常量 AspectRatio,就只有一份拷贝。
类属常量
为了将一个常量的作用范围限制在一个类内,你必须将它作为一个类的成员,而且为了确保它最多只有一个常量拷贝,你还必须把它声明为一个静态成员。
class GamePlayer {
private:
static const int NumTurns = 5; //@ constant declaration
int scores[NumTurns]; //@ use of constant
...
};
上面 NumTurns 是 declaration(声明),而不是 definition(定义)。
通常,C++ 要求你为你使用的任何东西都提供一个定义,但是一个静态整型族(例如:integer,char,bool)的 类属常量是一个例外。只要你不去取得它们的地址,你可以只声明并使用它,而不提供它的定义。如果你要取得一个类属常量的地址,或者你使用的编译器在你没有取得地址时也不正确地要求定义的话,可以提供一个独立的定义:
const int GamePlayer::NumTurns;
- 应该把它放在一个实现文件而非头文件中。
- 因为类属常量的初始值在声明时已经提供初值,在此无须重复给初值
- 定义时不需要 static 关键字。
没有办法使用 #define 来创建一个类属常量,因为 #defines 不考虑作用范围。一旦一个宏被定义,它将大范围影响你的代码(除非在后面某处存在 #undefine)。
the enum hack
一个枚举类型的值可以用在一个需要 int 的地方。
class GamePlayer {
private:
enum { NumTurns = 5 };
int scores[NumTurns];
...
};
可以合法地取得一个 const 的地址,但不能合法地取得一个 enum 的地址,这正像同样不能合法地取得一个 #define 的地址。如果你不希望人们得到你的整型族常量的指针或引用,枚举就是强制约束这一点的好方法。
inline 函数代替 #define
//@ call f with the maximum of a and b
#define CALL_WITH_MAX(a, b) f((a) > (b) ? (a) : (b))
无论何时,你写这样一个宏,都必须记住为宏体中所有的参数加上括号。否则,当其他人在表达式中调用了你的 宏,你将陷入麻烦。但是,即使你确实做到了这一点,你还是会看到意想不到的事情发生:
int a = 5, b = 0;
CALL_WITH_MAX(++a, b); //@ a is incremented twice
CALL_WITH_MAX(++a, b+10); //@ a is incremented once
可以通过一个内联函数的模板来获得宏的效率,以及完全可预测的行为和常规函数的类型安全:
template<typename T>
inline void callWithMax(const T& a, const T& b)
{
f(a > b ? a : b);
}
因为 callWithMax 是一个真正的函数,它遵循函数的作用范围和访问规则。例如,谈论一个类的私有的内联函数会获得正确的理解,但是用宏就无法做到这一点。
总结
- 对于简单常量,使用 const 代替宏定义,其优点:
- const 常量能够出现在符号表中,方便调试。
- 宏定义因为进行的宏替换,有时候会造成代码冗余,const 常量能够很好的避免这个问题。
- const 常量可以作为类属成员,#define 则毫无封装性。
- 整型族类属常量可以在类中声明时直接初始化。
- enum 也可以作为整型常量使用,并且无法取得其地址。
- 使用内联函数代替宏定义的函数将会在不损失效率的情况下降低发生错误的可能性。