1. 有些情况下,宁可以编译器替换预处理器,因为#define并不被视为语言的一部分从而导致某些问题.
2. 不带参数的宏展开引起的符号"丢失"问题.
例如,对于"#define ASPECT_RATIO 1.635",编译器在处理源代码之前ASPECT_RATIO就已经被替换为1.635,于是记号名称有可能没有进入记号表(symbol table)内.如果由于运用此常量而获得一个编译错误,错误信息可能会提到1.635而不是ASPECT_RATIO,追踪它将会浪费时间.
解决方案时以一个常量替换上面的宏(#define): const double AspectRatio=1.635;
这可以解决1所提出的问题,此外对于浮点常量(folating point const)而言,使用常量可能比使用#define导致较小量的码,因为它解决了预处理器盲目的将ASPECT_RATIO替换为1.635从而导致目标码出现多份1.635的情况.
种特殊情况值得注意:
第一,对于常量的指针,有必要将其声明为const,例如const double* pta;
第二,如果要将常量的作用域限制在class内,需要让它成为class的一个成员:而为确保此常量至多有一份实体,需将它设为static,例如:
class GamePlayer{ private: static const int NumTurns=5; int scores[NumTurns]; ... };
对于静态成员,C++标准规定必须在类定义体之外定义它们的定义式,然而如果它是整型变量,只要不取它们的地址,则可以只声明它们而无需定义式如果编译器坚持要看到一个定义式,则必须在类外提供定义式如下:
const int GamePlayer::NumTurns;(由于const静态常量在声明时已获初值,定义时不可再设初值).
然而,由于旧式编译器有可能不允许const静态成员在声明时设初值以及所谓的"in-class 初值设定"只允许对整数常量进行,那么需要将初值放在定义式:
class CostEstimate{ private: static const double FudgeFactor;//static 常量声明 ... //位于头文件内 } const double CostEstimate::FudgeFactor=1.35; //位于实现文件内
由此可能引发另外一个问题:当在class编译期需要该常量值时(例如在上述的GamePlayer::scores的数组声明式中,编译期必须在编译时知道数组的大小),如果编译期不支持"static整型const常量"完成"in-class 初值设定",可改用所谓的"the enum hack"补偿做法,其理论基础是:"一个属于枚举类型(enumerated type)的数值可权充ints被使用",于是GamePlayer可定义如下:
class GamePlayer{ privete: enum{NumTurns=5}; int scores[NumTurns]; ... };
基于数个理由enum hack值得我们认识:
1). enum hack的行为比较像#define而不像const,enums和#defines一样不会导致内存分配(有时候编译器可能会为const对象分配空间),因而取一个enum和#define的地址都不合法.
2). 实用主义,许多代码用了它,enum是模板元编程(template metaprogramming)的基础技术.
3. 带参数的宏引起的括号繁琐问题.
对于带参数的宏,尽管它比起函数具有不占内存的优点,但由于它只做简单的文本替换,因此可能引发其他问题.
例如: #define SQUARE(a) a*a;
对于SQUARE(1+2)展开后实际上为1+2*1+2,显然与初衷背离,,而为了避免这种情况,需要将SQUARE重新做以下定义:SQUARE(a) (a)*(a),这使得宏定义显得繁琐而且增加了出错的可能性.
对于以上问题,可以用template inline函数解决:
template<typename> inline void suqare(T a){ return a*a; }
template inline 函数同时具备宏带来的效率以及一般函数行为的可预料性和类型安全性,此外,由于square是个真正的函数,它遵循作用域和访问规则,例如可以写一个class内的private inline函数,而宏定义没有此功能.
4. 总结:对于单纯常量最好以const对象或enums替换#defines;
对于带参数的宏(macros),最好改用inline函数替换#defines;
需要注意的是,尽管宏定义的需求降低了,在条件编译以及调试输出宏等方面它仍然是不可替代的.