# Item 2: 用 consts, enums 和 inlines 取代 #defines
作者:Scott Meyers
译者:fatalerror99 (iTePub's Nirvana)
发布:http://blog.csdn.net/fatalerror99/
这个 Item 改名为“用 compiler(编译器)取代 preprocessor(预处理器)”也许更好一些,因为 #define 根本就没有被看作是语言本身的一部分。这是它很多问题中的一个。当你像下面这样做:
```
#define ASPECT_RATIO 1.653
```
compiler(编译器)也许根本就没有看见这个符号名 ASPECT_RATIO,在 compiler(编译器)得到源代码之前,这个名字就已经被 preprocessor(预处理器)消除了。结果,名字 ASPECT_RATIO 可能就没有被加入 symbol table(符号表)。如果在编译的时候,发现一个 constant(常量)使用的错误,你可能会陷入混乱之中,因为错误信息中很可能用 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
```
作为一个 language constant(语言层面上的常量),AspectRatio 被 compilers(编译器)明确识别并确实加入 symbol table(符号表)。另外,对于 floating point constant(浮点常量)(比如本例)来说,使用 constant(常量)比使用 #define 能产生更小的代码。这是因为 preprocessor(预处理器)盲目地用 1.653 置换 macro name(宏名字)ASPECT_RATIO,导致你的 object code(目标代码)中存在多个 1.653 的拷贝,如果使用 constant(常量)AspectRatio,就绝不会产生多于一个的拷贝。
用 constant(常量)代替 #defines 时,有两个特殊情况值得提出。首先是关于 constant pointers(常量指针)的定义。因为 constant definitions(常量定义)通常被放在 header files(头文件)中(这样它们就可以被包含在多个 source files(源文件)中),除了 pointer(指针)指向的目标是常量外,pointer(指针)本身被声明为 const 更加重要。例如,在头文件中定义一个 constant char\*-based string(基于 char\* 的字符串常量)时,你必须写两次 const:
```
const char * const authorName = "Scott Meyers";
```
对于 const(特别是与 pointers(指针)相结合时)的意义和使用的完整讨论,请参见 Item 3。然而在此值的一提的是,string objects(对象)通常比它的 char\*-based(基于 char\*)的祖先更可取,所以,更好的 authorName 的定义方式如下:
```
const std::string authorName("Scott Meyers");
```
第二个特殊情况涉及到 class-specific constants(类属(类内部专用的)常量)。为了将一个 constant(常量)的作用范围限制在一个 class(类)内,你必须将它作为一个类的 member(成员),而且为了确保它最多只有一个 constant(常量)拷贝,你还必须把它声明为一个 static member(静态成员)。
```
class GamePlayer {
private:
static const int NumTurns = 5; // constant declaration
int scores[NumTurns]; // use of constant
...
};
```
你从上面只看到了 NumTurns 的 declaration(声明),而不是 definition(定义)。通常,C++ 要求你为你使用的任何东西都提供一个 definition(定义),但是一个 static(静态)的 integral type(整型族)(例如:integers(整型),chars,bools)的 class-specific constants(类属常量)是一个例外。只要你不去取得它们的 address(地址),你可以只声明并使用它,而不提供它的 definition(定义)。如果你要取得一个 class constant(类属常量)的 address(地址),或者你使用的 compiler(编译器)在你没有取得 address(地址)时也不正确地要求 definition(定义)的话,你可以提供如下这样一个独立的 definition(定义):
```
const int GamePlayer::NumTurns; // definition of NumTurns; see
// below for why no value is given
```
你应该把它放在一个 implementation file(实现文件)而非 header file(头文件)中。因为 class constants(类属常量)的 initial value(初始值)在声明时已经提供(例如:NumTurns 在定义时被初始化为 5),因此在定义处允许没有 initial value(初始值)。
注意,顺便提一下,没有办法使用 #define 来创建一个 class-specific constant(类属常量),因为 #defines 不考虑 scope(作用范围)。一旦一个 macro(宏)被定义,它将大范围影响你的代码(除非在后面某处存在 #undefed)。这就意味着,#defines 不仅不能用于 class-specific constants(类属常量),而且不能提供任何形式的 encapsulation(封装),也就是说,没有类似 "private"(私有)#define 的东西。当然,const data members(const 数据成员)是能够被封装的,NumTurns 就是如此。
比较老的 compilers(编译器)可能不接受上面的语法,因为它习惯于将一个 static class member(静态类成员)在声明时就获得 initial value(初始值)视为非法。而且,in-class initialization (类内初始化)仅仅对于 integral types(整型族)和 constants(常量)才被允许。如果上述语法不能使用,你可以将 initial value(初始值)放在定义处:
```
class CostEstimate {
private:
static const double FudgeFactor; // declaration of static class
... // constant; goes in header file
};
const double // definition of static class
CostEstimate::FudgeFactor = 1.35; // constant; goes in impl. file
```
这就是你所要做的全部。仅有的例外是当在类的编译期需要 value of a class constant(一个类属常量的值)的情况,例如前面在声明 array(数组)GamePlayer::scores 时(compilers(编译器)必须在编译期知道 array(数组)的 size(大小))。如果 compilers(编译器)(不正确地)禁止这种关于 static integral class constants(静态整型族类属常量)的 initial values(初始值)的使用方法的 in-class specification(规范),一个可接受的替代方案被亲切地(并非轻蔑地)昵称为 "the enum hack"。这项技术获得以下事实的支持:一个 enumerated type(枚举类型)的值可以用在一个需要 ints 的地方。所以 GamePlayer 可以被如下定义:
```
class GamePlayer {
private:
enum { NumTurns = 5 }; // "the enum hack" - makes
// NumTurns a symbolic name for 5
int scores[NumTurns]; // fine
...
};
```
the enum hack 有几个值得被人所知的原因。首先,the enum hack 的行为在几个方面上更像一个 #define 而不是 const,而有时这正是你所需要的。例如:可以合法地取得一个 const 的 address(地址),但不能合法地取得一个 enum 的 address(地址),这正像同样不能合法地取得一个 #define 的 address(地址)。如果你不希望人们得到你的 integral constants(整型族常量)的 pointer(指针)或 reference(引用),enum(枚举)就是强制约束这一点的好方法。(关于更多的通过编码的方法强制执行设计约束的方法,参见 Item 18。)同样,使用好的 compilers(编译器)不会为 integral types(整型族类型)的 const objects(const 对象)分配多余的内存(除非你创建了这个对象的指针或引用),即使拖泥带水的 compilers(编译器)乐意,你也决不会乐意为这样的 objects(对象)分配多余的内存。像 #defines 和 enums(枚举)就绝不会导致这种 unnecessary memory allocation(不必要的内存分配)。
需要知道 the enum hack 的第二个理由是纯粹实用主义的,大量的代码在使用它,所以当你看到它时,你要认识它。实际上,the enum hack 是 template metaprogramming(模板元编程)的一项基本技术(参见 Item 48)。
回到 preprocessor(预处理器)上来,#define 指令的另一个普遍的(不好的)用法是实现看来像函数,但不会引起一个函数调用的开销的 macros(宏)。以下是一个用较大的宏参数调用函数 f 的 macro(宏):
```
// call f with the maximum of a and b
#define CALL_WITH_MAX(a, b) f((a) > (b) ? (a) : (b))
```
这样的 macro(宏)有数不清的缺点,想起来就让人头疼。
无论何时,你写这样一个 macro(宏),都必须记住为 macro body(宏体)中所有的 arguments(参数)加上括号。否则,当其他人在表达式中调用了你的 macro(宏),你将陷入麻烦。但是,即使你确实做到了这一点,你还是会看到意想不到的事情发生:
```
int a = 5, b = 0;
CALL_WITH_MAX(++a, b); // a is incremented twice
CALL_WITH_MAX(++a, b+10); // a is incremented once
```
这里,调用 f 之前 a 递增的次数取决于它和什么进行比较!
幸运的是,你并不是必须要和这样不知所云的东西打交道。你可以通过一个 inline function(内联函数)的 template(模板)来获得 macro(宏)的效率,以及完全可预测的行为和常规函数的类型安全(参见 Item 30):
```
template<typename T> // because we don't
inline void callWithMax(const T& a, const T& b) // know what T is, we
{ // pass by reference-to-
f(a > b ? a : b); // const - see Item 20
}
```
这个 template(模板)产生一组函数,每一个获得两个相同类型的对象并使用其中较大的一个调用 f。这样就不需要为函数体内部的参数加上括号,也不需要担心多余的参数解析次数,等等。此外,因为 callWithMax 是一个真正的函数,它遵循函数的作用范围和访问规则。例如,谈论一个类的私有的 inline function(内联函数)会获得正确的理解,但是用 macro(宏)就无法做到这一点。
为了得到 consts,enums 和 inlines 的可用性,你需要尽量减少 preprocessor(预处理器)(特别是 #define)的使用,但还不能完全消除。#include 依然是基本要素,而 #ifdef/#ifndef 也继续扮演着重要的角色。现在还不是让 preprocessor(预处理器)完全退休的时间,但你应该给它漫长而频繁的假期。
Things to Remember
* 对于 simple constants(简单常量),用 const objects(const 对象)或 enums(枚举)取代 #defines。
* 对于 function-like macros(类似函数的宏),用 inline functions(内联函数)取代 #defines。
- Preface(前言)
- Introduction(导言)
- Terminology(术语)
- Item 1: 将 C++ 视为 federation of languages(语言联合体)
- Item 2: 用 consts, enums 和 inlines 取代 #defines
- Item 3: 只要可能就用 const
- Item 4: 确保 objects(对象)在使用前被初始化
- Item 5: 了解 C++ 为你偷偷地加上和调用了什么函数
- Item 6: 如果你不想使用 compiler-generated functions(编译器生成函数),就明确拒绝
- Item 7: 在 polymorphic base classes(多态基类)中将 destructors(析构函数)声明为 virtual(虚拟)
- Item 8: 防止因为 exceptions(异常)而离开 destructors(析构函数)
- Item 9: 绝不要在 construction(构造)或 destruction(析构)期间调用 virtual functions(虚拟函数)
- Item 10: 让 assignment operators(赋值运算符)返回一个 reference to *this(引向 *this 的引用)
- Item 11: 在 operator= 中处理 assignment to self(自赋值)
- Item 12: 拷贝一个对象的所有组成部分
- Item 13: 使用对象管理资源
- Item 14: 谨慎考虑资源管理类的拷贝行为
- Item 15: 在资源管理类中准备访问裸资源(raw resources)
- Item 16: 使用相同形式的 new 和 delete
- Item 17: 在一个独立的语句中将 new 出来的对象存入智能指针
- Item 18: 使接口易于正确使用,而难以错误使用
- Item 19: 视类设计为类型设计
- Item 20: 用 pass-by-reference-to-const(传引用给 const)取代 pass-by-value(传值)
- Item 21: 当你必须返回一个对象时不要试图返回一个引用
- Item 22: 将数据成员声明为 private
- Item 23: 用非成员非友元函数取代成员函数
- Item 24: 当类型转换应该用于所有参数时,声明为非成员函数
- Item 25: 考虑支持不抛异常的 swap
- Item 26: 只要有可能就推迟变量定义
- Item 27: 将强制转型减到最少
- Item 28: 避免返回对象内部构件的“句柄”
- Item 29: 争取异常安全(exception-safe)的代码
- Item 30: 理解 inline 化的介入和排除
- Item 31: 最小化文件之间的编译依赖
- Item 32: 确保 public inheritance 模拟 "is-a"
- Item 33: 避免覆盖(hiding)“通过继承得到的名字”
- Item 34: 区分 inheritance of interface(接口继承)和 inheritance of implementation(实现继承)
- Item 35: 考虑可选的 virtual functions(虚拟函数)的替代方法
- Item 36: 绝不要重定义一个 inherited non-virtual function(通过继承得到的非虚拟函数)
- Item 37: 绝不要重定义一个函数的 inherited default parameter value(通过继承得到的缺省参数值)
- Item 38: 通过 composition(复合)模拟 "has-a"(有一个)或 "is-implemented-in-terms-of"(是根据……实现的)
- Item 39: 谨慎使用 private inheritance(私有继承)
- Item 40: 谨慎使用 multiple inheritance(多继承)
- Item 41: 理解 implicit interfaces(隐式接口)和 compile-time polymorphism(编译期多态)
- Item 42: 理解 typename 的两个含义
- Item 43: 了解如何访问 templatized base classes(模板化基类)中的名字
- Item 44: 从 templates(模板)中分离出 parameter-independent(参数无关)的代码
- Item 45: 用 member function templates(成员函数模板) 接受 "all compatible types"(“所有兼容类型”)
- Item 46: 需要 type conversions(类型转换)时在 templates(模板)内定义 non-member functions(非成员函数)
- Item 47: 为类型信息使用 traits classes(特征类)
- Item 48: 感受 template metaprogramming(模板元编程)
- Item 49: 了解 new-handler 的行为
- Item 50: 领会何时替换 new 和 delete 才有意义
- Item 51: 编写 new 和 delete 时要遵守惯例
- Item 52: 如果编写了 placement new,就要编写 placement delete
- 附录 A. 超越 Effective C++
- 附录 B. 第二和第三版之间的 Item 映射