# 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 映射