Effective C++ Item 2:Prefer constS, enumS, and inlineS to #defineS
2010-05-05 15:37
483 查看
这个条款叫“喜欢编译器胜过预处理器”更贴切。因为#define不被视为语言的一部分。当你这样写时,
符号名ASPECT_RATIO对于编译器来讲是不可见的;在源程序到达编译器之前,ASPECT_RATIO很可能被预处理器删除了。所以,ASPECT_RATIO符号名也许压根就没有进入符号表。这就会使你很困惑了,特别编译过程中涉及到这个常量的使用时,因为错误信息可能指的是1.653,而不是ASPECT_RATIO。如果ASPECT_RATIO在一个不是你写的头文件中定义时,你就不知道这个1.653到底是哪里冒出来的,而你还浪费时间跟踪这个变量。这个问题在符号调试程序(symbolic debugger)中也会出现。原因相同:你编程中用到的名字也许压根就不在符号表(symbol table)里面。
解决方案:用常量(constant)来替换宏(macro)
作为一个常量,AspectRatio对编译器是可见的,并且肯定是进入符号表的。另外对于浮点常量而言,使用常量(constant)比使用#define产生更少的代码量。因为预处理器(preprocessor)盲目地把宏名ASPECT_RATIO替换成1.653,这很可能导致了你的目标代码(object code)中含有1.653的若干个拷贝,但用常量AspectRatio就不同了,顶多会出现一份拷贝。(AspectRatio should never result in more than one copy)。
当用常量替换#define时,有二点值得注意。
1、定义常量指针(constant pointers)。因为典型的常量定义是在头文件中(许多源文件包含这些头文件),指针有必要被声明为const型(无论所指向的内容是否为const型)。例如,在头文件中定义一个常量char*字符串,你需要写const二次:
一般情况下,更推荐使用string对象,所以authorName通常这样定义:
2、类专属常量(class-specific constants)
为了限制一个常量的范围在一个类内,你需要将这个常量定义为类的一个成员(member),为了保证最多只有一份变量的拷贝,你需要将其定义为static成员:
上面你看到的是NumTurns的声明(declaration),不是定义(definition)。通常C++要求你对你使用的任何东西提供一个定义(definition),但如果该类专属常量(class-specific constants)是static且是integral type(如,integers, chars, bools)则无需特殊处理。只要你不使用它们的地址(address),你就可以不用定义而声明和使用它们(declare them and use them without providing a definition)。如果你确实需要使用一个类常量的地址,或者当你不使用地址时你的编译器坚持要你给一个定义,你必须另外提供这样的定义式:
请把这个放在一个实现文件(impl. file)而不是一个头文件(head file)。这是因为类常量的初值是在这个常量被声明的时候给出的(如,NumTurns在声明的时候被赋初始5),因此在定义时不需要再设初值(no initial value is permitted at the point of definition)。
顺带说一下,注意我们无法用#define来创建一个类专属常量(class-specific constant),因为#define没有作用域(scope)的概念。一旦定义了一个宏,它就在其后的编译过程中有效(除非在某处被#undef)。这意味着#defines不仅不能用来定义类专属常量(class-specific constant),也不能提供封装性(encapsulation),如就没有private #define这种东西。而当然const数据成员(data member)是可以被封装的,就像NumTurns一样。
旧式的编译器也许不支持上述语法,因为不允许在一个静态类成员(static class member)声明的时候(at its point of declaration)提供一个初始值。并且,“in-class”initialization只允许对integral types进行且仅仅对于常量。如果你的编译器不允许上述语法,你可以把初值放在定义式中:
这几乎是你在任何时候都需要做的事情。唯一的一个例外是:
当你在类编译期间需要一个类常量值,例如在上述的GamePlayer::scores的数组声明中(编译器坚持必须知道在编译期间知道数组大小)。这时万一你的编译器(错误地)不允许“static integral types 类常量”完成"in-class 初值设定",可改用所谓的"enum hack"的补偿做法。于是GamePlayer可以定义如下:
1、enum hack的行为某方面像#define而不像const,有时候这正是你想要的。
如取一个const的地址是合法的,但取一个enum的地址就不合法,而取一个#define的地址通常也不合法。如果你不想让别人获得一个pointer或reference指向你的某个integral constant,enum可以帮助你实现这个约束。此外,虽然优秀的编译器不会为"const objects of integral types"设定额外的存储空间(除非你想创建一个pointer或reference指向该对象),不够优秀的编译器却可能会如此,而这可能不是你想要的。Enums和#defines一样绝对不会导致非必要的内存分配。(unnecessary memory allocation)。
2、很多代码用enum hack,所以有必要了解一下。
再回头看看预处理器。别一个常见的#define误用情况是以它实现宏(macros)。宏看起来像函数,但不会导致函数调用(function call)带来的额外开销(overhead)。下面这个宏夹带着宏实参,调用函数f:
宏有太多的缺点,光想一想都令人头痛!
每次你写宏,你都需要记得在宏体里面把每个参数用括号给括起来。如果你没有这么做的话,当有人用一个表达式来调用宏时,就会出现问题:
在这里,a自增几次取决于它与什么数相比较!
不过幸运的是,你不需要忍受这些无聊的琐事。你可以获得得宏带来的效率以及一般函数的可预料行为和类型安全(type safety)----只要你写一个template inline函数。
这个template产生一系列的函数。每个函数接受两个同类型的对象,并且以其中较大者调用f。再也没有必要为每个参数打上括号了。也不用去担心参数到底被运算了几次等等。更进一步,因为callWithMax是一个实实在在的函数。它遵守作用域和访问规则(it obeys scope and access rules)。如,你绝对可以写出一个类私有内联函数(private inline function)。而通常用宏(macros)是不可能完成的。
有了constS,enumS和inlineS,我们对于预处理器的需求降低了,但并非完全消除。#include依然是必要的,而#ifdef/#ifndef也继续扮演着控制编译的重要角色。目前还不到预处理器全面引退的时候,但对于你,可以给它放一个长假啦!
请记住
1、对于单纯常量(simple constants),最好以const对象或enums替换#defines。
2、对于形似函数的宏(macros),最好改用inline函数替换#defines。
#define ASPECT_RATIO 1.653
符号名ASPECT_RATIO对于编译器来讲是不可见的;在源程序到达编译器之前,ASPECT_RATIO很可能被预处理器删除了。所以,ASPECT_RATIO符号名也许压根就没有进入符号表。这就会使你很困惑了,特别编译过程中涉及到这个常量的使用时,因为错误信息可能指的是1.653,而不是ASPECT_RATIO。如果ASPECT_RATIO在一个不是你写的头文件中定义时,你就不知道这个1.653到底是哪里冒出来的,而你还浪费时间跟踪这个变量。这个问题在符号调试程序(symbolic debugger)中也会出现。原因相同:你编程中用到的名字也许压根就不在符号表(symbol table)里面。
解决方案:用常量(constant)来替换宏(macro)
const double AspectRatio = 1.653; //uppercase names are usually for macros, hence the name change
作为一个常量,AspectRatio对编译器是可见的,并且肯定是进入符号表的。另外对于浮点常量而言,使用常量(constant)比使用#define产生更少的代码量。因为预处理器(preprocessor)盲目地把宏名ASPECT_RATIO替换成1.653,这很可能导致了你的目标代码(object code)中含有1.653的若干个拷贝,但用常量AspectRatio就不同了,顶多会出现一份拷贝。(AspectRatio should never result in more than one copy)。
当用常量替换#define时,有二点值得注意。
1、定义常量指针(constant pointers)。因为典型的常量定义是在头文件中(许多源文件包含这些头文件),指针有必要被声明为const型(无论所指向的内容是否为const型)。例如,在头文件中定义一个常量char*字符串,你需要写const二次:
const char* const authorName = "Scott Meyers";
一般情况下,更推荐使用string对象,所以authorName通常这样定义:
const std:string authorName("Scott Meyers");
2、类专属常量(class-specific constants)
为了限制一个常量的范围在一个类内,你需要将这个常量定义为类的一个成员(member),为了保证最多只有一份变量的拷贝,你需要将其定义为static成员:
class GamePlayer { private: static const int NumTurns = 5;//constant declaration int scores[NumTurns]; //use of constant ... };
上面你看到的是NumTurns的声明(declaration),不是定义(definition)。通常C++要求你对你使用的任何东西提供一个定义(definition),但如果该类专属常量(class-specific constants)是static且是integral type(如,integers, chars, bools)则无需特殊处理。只要你不使用它们的地址(address),你就可以不用定义而声明和使用它们(declare them and use them without providing a definition)。如果你确实需要使用一个类常量的地址,或者当你不使用地址时你的编译器坚持要你给一个定义,你必须另外提供这样的定义式:
const int GamePlayer::NumTurns; //definition of NumTurns; see below for why no value is given
请把这个放在一个实现文件(impl. file)而不是一个头文件(head file)。这是因为类常量的初值是在这个常量被声明的时候给出的(如,NumTurns在声明的时候被赋初始5),因此在定义时不需要再设初值(no initial value is permitted at the point of definition)。
顺带说一下,注意我们无法用#define来创建一个类专属常量(class-specific constant),因为#define没有作用域(scope)的概念。一旦定义了一个宏,它就在其后的编译过程中有效(除非在某处被#undef)。这意味着#defines不仅不能用来定义类专属常量(class-specific constant),也不能提供封装性(encapsulation),如就没有private #define这种东西。而当然const数据成员(data member)是可以被封装的,就像NumTurns一样。
旧式的编译器也许不支持上述语法,因为不允许在一个静态类成员(static class member)声明的时候(at its point of declaration)提供一个初始值。并且,“in-class”initialization只允许对integral types进行且仅仅对于常量。如果你的编译器不允许上述语法,你可以把初值放在定义式中:
class CostEstimate { private: static const double FudgeFactor; //declaration of static class constant; goes in header file }; const double CostEstimate::FudgeFactor = 1.35; //definition of static class constant; goes in impl. file
这几乎是你在任何时候都需要做的事情。唯一的一个例外是:
当你在类编译期间需要一个类常量值,例如在上述的GamePlayer::scores的数组声明中(编译器坚持必须知道在编译期间知道数组大小)。这时万一你的编译器(错误地)不允许“static integral types 类常量”完成"in-class 初值设定",可改用所谓的"enum hack"的补偿做法。于是GamePlayer可以定义如下:
class GamePlayer { private: enum {NumTurns = 5 }; //"the enum hack"令NumTurns 成为5的一个符号名 int scores[NumTurns]; //OK };
1、enum hack的行为某方面像#define而不像const,有时候这正是你想要的。
如取一个const的地址是合法的,但取一个enum的地址就不合法,而取一个#define的地址通常也不合法。如果你不想让别人获得一个pointer或reference指向你的某个integral constant,enum可以帮助你实现这个约束。此外,虽然优秀的编译器不会为"const objects of integral types"设定额外的存储空间(除非你想创建一个pointer或reference指向该对象),不够优秀的编译器却可能会如此,而这可能不是你想要的。Enums和#defines一样绝对不会导致非必要的内存分配。(unnecessary memory allocation)。
2、很多代码用enum hack,所以有必要了解一下。
再回头看看预处理器。别一个常见的#define误用情况是以它实现宏(macros)。宏看起来像函数,但不会导致函数调用(function call)带来的额外开销(overhead)。下面这个宏夹带着宏实参,调用函数f:
// 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
在这里,a自增几次取决于它与什么数相比较!
不过幸运的是,你不需要忍受这些无聊的琐事。你可以获得得宏带来的效率以及一般函数的可预料行为和类型安全(type safety)----只要你写一个template inline函数。
template <typename T> inline void callWithMax(const T&a, const T& b) //because we don't know what T is, we pass by reference-to-const { f(a > b ? a : b); }
这个template产生一系列的函数。每个函数接受两个同类型的对象,并且以其中较大者调用f。再也没有必要为每个参数打上括号了。也不用去担心参数到底被运算了几次等等。更进一步,因为callWithMax是一个实实在在的函数。它遵守作用域和访问规则(it obeys scope and access rules)。如,你绝对可以写出一个类私有内联函数(private inline function)。而通常用宏(macros)是不可能完成的。
有了constS,enumS和inlineS,我们对于预处理器的需求降低了,但并非完全消除。#include依然是必要的,而#ifdef/#ifndef也继续扮演着控制编译的重要角色。目前还不到预处理器全面引退的时候,但对于你,可以给它放一个长假啦!
请记住
1、对于单纯常量(simple constants),最好以const对象或enums替换#defines。
2、对于形似函数的宏(macros),最好改用inline函数替换#defines。
相关文章推荐
- effective C++ Item 2: Prefer consts, enums, and inlines to #defines
- Item 2: Prefer consts, enums, and inlines to #defines(Effective C++)
- Item 02: Prefer consts, enums, and inlines to #defines
- Effective C++读书笔记(3)-Item 2: Prefer consts, enums, and inlines to #defines
- Item 02 : Prefer consts, enums, and inlines to #defines
- Effective C++ 2. Prefer consts, enums, and inlines to #defines
- 条款02:尽量以const, enum, inline替换#define(Prefer consts,enums, and inlines to #defines)
- Effective C++读书笔记——(一)Prefer consts, enums, and inlines to #defines
- 《Effctive C++》读书笔记--(02)Prefer consts,enums,and inlines to #defines
- effective c++ prefer const,enum, inline to #defines
- effective C++ 2 prefer consts, enums and inlines to #defines
- Effective C++ (3rd Ed) 读书笔记(一)Item 2: Prefer constS, enumS, and inlineS to #defineS
- 笔记:Perfer consts,enums,and inlines to #define
- Effective C++ Item 15 Provide access to raw resources in resource-managing classes
- Effective C++(Item1) Prefer const and inline to #define
- [翻译] Effective C++, 3rd Edition, Item 2: 用 consts, enums 和 inlines 取代 #defines
- Effective C++ Item 18 Make interfaces easy to use correctly and hard to use incorrectly
- Effective C++,rule 2,Prefer const,enum and inlines to #define
- Effective C++ Item 10,11 Have assignment operators return a reference to *this Handle assignment to self in operator =
- Effective C++ Item 16 Use the same form in corresponding uses of new and delete