Prefer consts,enums,and inlines to #defines.
#define ASPECT_RATIO 1.653
记号名称ASPECT_RATIO也许从未被编译器所见;也许在编译器开始处理源码之前他就被预处理器移走了。于是记号名称ASPECT_RATIO有可能没进入记号表(symbol table)内。于是当你运用此常量但获得一个编译错误信息时,可能会带来困惑,因为这个错误信息也许会提到1.653而不是ASPECT_RATIO。如果ASPECT_RATIO被定义在一个非你所写的头文件内,你肯定对1.653以及它来自何处毫无概念,于是你将追踪它而浪费时间。这个问题也可能出现在记号式调试器(symbolic debugger)中,原因相同:你所使用的名称可能并未进入记号表(symbol table)。
解决之道是以一个常量替换上述的宏:const double AspectRatio = 1.653;作为一个语言常量,AspectRatio肯定会被编译器看到,当然就会进入记号表内。此外对浮点常量而言,使用常量可能比使用#define导致较小量的码,因为预处理器“盲目地将宏名称ASPECT_RATIO替换为1.653”可能导致目标码(object code)出现多份1.653,若改用常量AspectRatio绝不会出现相同情况。
当我们以常量替换#defines,有两种特殊情况值得说说。第一是常量指针(constant pointers)。如果要定义一个常量的char*-base字符串,你必须写const两次:const char * const authorName = “Scott Meyers”;第二个值得注意的是class专属常量。为了将常量的作用域(scope)限制于class内,你必须让它成为class的一个成员;而为确保此常量至多只有一份实体,你必须让它成为一个static成员。
class GamePlayer{
private:static const int NumTurns = 5;int scores[NumTurns];
};
然而你所看到的是NumTurns的声明式而非定义式。通常C++要求你对你所使用的任何东西提供一个定义式,但如果它是个class专属常量又是static且为整数类型(integral type,例如ints,chars,bools),则需特殊处理。只要不取它们的地址,你可以声明并使用它们而无须提供定义式。但如果你取某个class专属常量的地址,或纵使你不取其地址而你的编译器却(不正确地)坚持要看到一个定义式,你就必须另外提供定义式如下:const int GamePlayer::NumTurns;请把这个式子放进一个实现文件而非头文件。由于class常量已在声明时获得初值。因此定义时不可以再设初值。
顺便一提,请注意,我们无法利用#define创建一个class专属常量,因为#define并不重视作用域(scope)。一旦宏被定义,它就在其后的编译过程中有效(除非在某处被#undef)。
旧式编译器也许不支持上述语法,它们不允许static成员在其声明式上获得初值。此外所谓的“in-class初始设定”也只允许对整数常量进行。如果你的编译器不支持上述语法,你可以将初值放在定义式:
class CostEstimate {
private:static const double FudgeFactor;
};const double CostEstimate::FudgeFactor = 1.35;
这几乎是你在任何时候唯一需要做的事。唯一例外是当你在class编译期间需要一个class常量值,例如在上述的GamePlayer::scores的数组声明式中(是的,编译器坚持必须在编译期间知道数组的大小)。这时候万一你的编译器(错误的)不允许“static整数型class常量”完成“int class初值设定”,可改用所谓的“the enum hack”补偿做法。其理论基础是:“一个属于枚举类型(enumerated type)的数值可权充ints被使用”,于是GamePlayer可定义如下:
class GamePlayer{
private:enum { NumTurns = 5 };int scores[NumTurns];
};
基于数个理由enum hack值得我们认识。第一,enum hack的行为某方面说比较像#define而不像const,有时候这正是你想要的。例如取一个const的地址是合法的,但取一个enum的地址就不合法,而取一个#define的地址通常也不合法。此外虽然优秀的编译器不会为“整数型const对象”设定另外的存储空间(除非你创建一个pointer或reference指向该对象),不够优秀的编译器却可能如此,而这可能是你不想要的。Enums和#defines一样绝不会导致非必要的内存分配。认识enum hack的第二个理由纯粹是为了实用主义。许多代码用了它,所以看到它时你必须认识它。事实上“enum hack”是template metaprogramming(模板元编程)的基础技术。
另一个常见的#define误用情况时以它实现宏(macros)。宏看起来像函数,但不会招致函数调用(function call)带来的额外开销。下面这个宏夹带着宏实参,调用函数f:
#define CALL_WITH_MAX(a, b) f((a) > (b) ? (a) : (b))int a = 5, b = 0;CALL_WITH_MAX(++a, b); //a被累加二次
CALL_WITH_MAX(++a, b+10); //a被累加一次
纵使你为所有的实参加上小括号,但在调用f之前,a的递增次数竟然取决于“它被拿来和谁比较”!
幸运的是你不需要为这种无聊事情提供温床。你可以获得宏带来的效率以及一般函数的所有可预料行为和类型安全性(type safety)--只要你写出template inline函数。
template<typename T>
inline void callWithMax(const T& a, const T& b)
{f(a > b ? a : b);
}
请记住
对于单纯常量,最好以const对象或enums替换#defines
对于形似函数的宏,最好改用inline函数替换#defines