關於EffectIve C++ 的總結(侯捷翻譯版)

JUAN425發表於2014-08-01

C++ 提供四種不同而有相輔相成的程式設計範型(programming paradigms) 如下:

(1)procedural based 

(2)object based

(3)object oriented

(4)generics


item1: 將C++視為一個語言聯邦

這個語言聯邦有四個“州”(即C++包含這四個部分):

(1)C, C++ 起源於C, 一開始只是在C的基礎上加上了一些物件導向的特性。 C++ 的最初名稱為C with classes。 發展到今天, C 語言沒有的特性如下: 沒有模板(template), 沒有異常(exception), 沒有過載(overloading)等等。

(2)object-oriented C++。 這是C with class所訴求的。 classes, 封裝(encapsulation), 繼承(Inheritance), 多型(Polymorphism), Virtual 函式(動態繫結)等等。 這是物件導向的。

(3)Template C++. 這是C++ 泛型程式設計的部分。 模板很強大。

(4)STL. STL 是一個template程式庫。 具有容器, 迭代器, 演算法, 函式物件。


Item 2: 儘量使用const, enum, inline, 用於替換由#define的macro(巨集)

這個條款的意思是“寧可以編譯器替換前處理器”。 因為#define 不被視為語言的一部分。 例如:

#define ASPECT_RATIO  1.653

記號 ASPECT_RATIO 並不被我們的Compiler看見。 編譯器在處理我們的source code的之前, 記號ASPECT_RATIO 就被前處理器移走了。 ASPECT_RATIO 就沒有進入Compiler 用於記錄源程式的記號表中(symbol table).  所以出現錯誤的時候。 錯誤資訊會提到1.653而不是ASPECT_RATIO。 如果ASPECT_RATIO被定義在一個非你所寫的標頭檔案中, 我們可能對於1.653 毫無概念。 除錯起來也很費時間。 

解決之道是用常量替換上述的巨集(#deine):

const double AspectRation = 1.653; // 大寫名稱常用於巨集, 所以這裡改變名稱寫法
作為語言常量, AspectRatio 肯定會被編譯器看到。 當然也會進入幾號表內。 另外, 如果使用const (常量)代替#define 巨集的另外一個好處是導致記憶體小。 因為前處理器會盲目的將ASPECT_RATIO 替換為1.653, 這樣我們的object code 就會有多份1.653。 但是對於常量Aspect_Ratio , 這是絕對不會發生的。  因為如果我們的含有常量Aspect_Ratio 定義的標頭檔案被多個source file 包含的時候, 我們的編譯器會產生對這個常量重複定義的錯誤, 即a linker error from multiple definitions of the symbol。  但是#define的標頭檔案被多個原始檔包含是, 編譯卻不會發生錯誤。 通常巨集#define 的記憶體為: 巨集的大小 x 包含含有巨集的標頭檔案的原始檔個數。


建立class的專屬常量。 也就是說將常量的作用域(scope)限制於class內, 你就必須讓常量稱為class的一個member。 為了確保常量至多隻有一份實體, 必須將其宣告為static 成員。

例如:

class GamePlayer {
   private:
      static const int NumTurns = 5; // 常量宣告式
int scores[NumTurns]; // 使用該常量
};



注意, 上面的NumTurns 是宣告式, 而非定義常量的式子。 

通常C++ 要求你對你所使用的任何東西提供一個定義式。 但是如果它是一個class的專屬常量, 又是static,並且為整形型別(Integral type, 例如int, char, bool), 則需要特別處理。 只要不取用它的地址, 我們可以宣告而無需提供定義式。 但是如果你取某個class的專屬常量的地址, 或者總是你不取, 但是你的編譯器卻(不正確的)堅持要看到一個定義式, 此時, 你就必須另外提供定義式如下:

const int GamePlayer::NumTurns; //NumTurns 的定義, 下面告訴你為什麼沒有給與數值
上述的這個式子應該放在實現檔案中, 而非標頭檔案中。 由於class的常量已經在宣告的時候獲得初值(為5), 所以定義的時候不可以再設初值。

我們無法使用#define 去建立一個class的專屬常量。 因為一巨集被定義, 它在其後的編譯過程中有效(除非在某處被# undef)。 這意味著巨集無法提供封裝性。


舊式編譯器一般不支援上述的語法。 如果你的編譯器不支援, 你可以將初值放在定義式中:


class CostEstimate {
   private:
      static const double FudgeFactor; // 常量宣告式
      ...
}; //  位於標頭檔案中


const double CostEstimate::FudgeFactor = 1.35; // 放置在實現檔案中

 這幾乎是你在任何時候唯一需要做的事。 唯一的例外是, 當你的class在編譯期間需要一個class的常量值。

例如, 在上述的GamePlayer::scores的陣列宣告式中(編譯器堅持在編譯期間知道陣列的大小(除非動態分配的動態陣列))。 這時候萬一你的編譯器(錯誤的)不允許“static const” 常量無法完成“in class 初值設定”, 那麼可以改用the enum hack 的補償做法。 

這樣做的理論基礎是: 一個屬於列舉型別的數值可充當ints 被使用。 這樣定義如下:

 

class GamePlayer {
   private:
      enum{NumTurns = 5}; 
int scores[NumTurns]; 
};

enum hack 的認識如下:

取一個const 的地址合法, 但是取一個enum 的地址是不合法的, 而去一個#define 的地址也是不合法的。


現在看看前處理器。

一個常見的#define的誤用情況是依他實現巨集(macros)。

巨集看起來像函式, 但是不會招致函式呼叫(function call )帶來的額外開銷。

巨集有很多的缺點, 想想就讓人痛苦。

無論何時, 定義巨集的時候, 需要在巨集中將所有的實參獎賞小括號。 否則別人呼叫起來就麻煩了。 幸運的是, 我們可以寫出template inline 函式, 一便獲得巨集的效率的同時, 由能夠避免巨集的缺點:

template <class T>
inline void callWithMax(const T&a, const T& b) {
   f(a>b ? a:b);
}
這樣決定較大者呼叫函式f。


有了const, enum, inline, 我們對前處理器的需求降低了。 但是#include 仍然必須, 而#ifndef等也繼續扮演著控制編譯的重要的角色。 

但是請記住:

(1)對於單純的常量, 最好以const 物件或者enum 去替換 #define。

(2)對於形似函式的巨集(macro), 最好改用inline 函式替換 #defines。 







class CostEstimate { private: static const double FudgeFactor; // 常量宣告式...}; // 位於標頭檔案中const double CostEstimate::FudgeFactor = 1.35

相關文章