天天看點

Effective C++之 Item 2: 用 consts, enums 和 inlines 取代 #defines

這個 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。

注:本文拷貝自fatalerror99 (iTePub's Nirvana)的翻譯

繼續閱讀