[转]Effective C++ 02 尽量以const, enum, inline替换#define 笔记

来源:互联网 发布:车管家软件是什么 编辑:程序博客网 时间:2024/06/06 03:54

Effective C++ 02 尽量以const, enum, inline替换#define 笔记

本条款或许改为“宁可以编译器替换预处理器”比较好,因为或许#define不被视为语言的一部分。

 

#define ASPECT_RATIO 1.653

     记号名称ASPECT_RATIO也许从未被编译器看见,在编译器开始处理源代码之前就被预处理器移走了。记号名称ASPECT_RATIO可能没有进入符号表,若被定义在非你所写的头文件内,编译出错时肯定对1.653毫无概念。

     解决方法用常量替换宏:

const double AspectRatio = 1.653

     作为语言常量AspectRatio肯定会被编译器看到,会进入符号表内。对浮点常量,使用常量可能比使用宏导致较小的代码量,预处理器“盲目将宏名称ASPECT_RATIO替换为1.653”导致目标码出现多份1.653,若改用常量AspectRatio绝不会出现相同的情况。

     用常量替换#defines,有两种特殊的情况:第一是定义常量指针。由于常量定义通常被放在头文件内,因此有必要将指针(而不是指针所指之物)声明为const。若要在头文件内定义一个常量的(不变的)char*-based字符串,必须写const两次:

const char* const authorName = "Scott Meyers"

string对象通常比其前辈char*-based合宜:

const std::string authorName("Scott Meyers"); 

     第二个值得注意的class专属常量。为了将常量的作用域限制于class内,必须让它成为class的一个成员,而为确保此常量至多只有一份实体,你必须让它成为一个static成员:

复制代码
1 class GamePlayer {
2 private:
3     static const int NumTurns = 5;    //常量声明式
4      int scores[NumTurns];             //使用该常量
5      
6 }; 
复制代码

这是NumTurns的声明式而非定义式。通常C++要求使用的东西提供定义式,但如果是class专属常量又是static且为整数类型,则需特殊处理。不取地址,可以声明并使用无须定义式。若取class专属常量地址,或你不取地址而编译器却要一个定义式,就必须定义如下:

const int GamePlayer::NumTurns; //NumTurns的定义 

请把定义式放进一个实现文件而非头文件。由于class常量已经在声明时获得初值,定义时不可以再设初值。

     #define无法创建一个class专属常量,也不能提供任何封装,而const常量成员变量是可以封装的。旧编译器若不支持static成员在其声明式上获得初值,可以将初值放在定义式。

     若编译器不允许static整数型class常量完成in class初值设定,可以改用“the enum hack”补偿做法。理论基础是:“一个属于枚举类型的数值可权充ints被使用”,GamePlayer可定义如下:

复制代码
1 class GamePlayer {
2 private:
3     enum { NumTurns = 5 } ;  //"the enum hack"
4     int scores[NumTurns];
5     
6 }; 
复制代码

认识enum hack的理由:第一,enum hack行为比较像#define而不像const,有时候这这是你想要的。取const的地址是合法的,取一个enum的地址就不合法,而取一个#define的地址通常也不合法。不想别人获得一个pointer或reference指向你的某个整数常量,enum可以帮助你实现这个约束。第二,为了实用主义。事实上"enum hack"是template metaprogramming(模板元编程)的基础技术。

     另一个常见的#define误用情况是以它实现宏。宏看起来像函数,但不会带来函数调用的额外开销。

//以a和b的较大值调用f

#define CALL_WITH_MAX(a, b) f((a) > (b) ? (a) : (b))

必须记住为宏中的所有实参加上小括号,否则再表达式中调用这个宏可能会遭遇麻烦。纵使为所有实参加上小括号,看看下面不可思议的事情

1 int a = 5, b = 0;
2 CALL_WITH_MAX(++a, b);    //a被累加二次
3 CALL_WITH_MAX(++a, b+10); //a被累加一次

在这里,调用f之前,a的递增次数竟然取决于"它被拿出来和谁比较"!

     你可以获得宏的效率以及一般函数的所有可预料行为和类型安全性,只要你写出template inline函数:

1 template<typename T>
2 inline void callWithMax(const T& a, const T& b)
3 {
4     f(a > b ? a : b);
5 }

这个template产出一群函数,每个函数都接受两个同型对象,并以其中较大者调用f。这里不需要在函数体中为参数加上括号,也不需要操心被核算(求值)多次。由于callWithMax是个真正的函数,遵守作用域和访问规则。例如你绝对可以写出一个“class 内的private inline函数”,一般而言宏无法完成此事。

     有了consts、enums、inlines,我们对预处理器(特别是#define)的需求降低了,但#include仍然是必需品,而#ifdef/#ifndef也继续扮演控制编译的重要角色。

请记住

对于单纯常量,最好以const对象或enums替换#defines。

对于形似函数的宏,最好改用inline函数替换#defines。