條款01:視C++ 為一個語言聯邦
View C++ as a federation of languages.
今天的C++ 已經是一個同時支持過程形式(procedural)、面向對象形式(object-oriented)、函數形式(functional)、泛型形式(generic)、元編程形式(metaprogramming)的語言。將C++ 視為一個由相關語言組成的聯邦而非單一語言。
(1)C:說到底C++ 仍是以C為基礎。C語言的局限:沒有模板(templates),沒有異常(exceptions),沒有重載(overloading)……
(2)Object-Oriented C++:類、封裝、繼承、多態、virtual函數……等等
(3)Template C++:泛型編程(自己最沒經驗的部分)
(4)STL。
次語言的切換時,高校編程守則的策略可能不同:
內置(C-like)類型:傳值優於傳引用;在STL中亦是如此,因為迭代器和函數對象都是在C指針之上塑造出來的。
Object-Oriented C++:由於用戶自定義構造函數和析構函數的存在,pass-by-reference-to-const更好;Template C++亦是如此。
· C++ 高效編程守則視狀況而變化,取決於你使用C++ 的哪一部分。
條款02:盡量以const,enum, inline替換 #define
Prefer consts,enums, and inlines to #defines.
寧可以編譯器替換預處理器,因為或許 #define不被視為語言的一部分。
#define ASPECT_RATIO 1.653
宏通常用大寫名稱標記,記號名稱ASPECT_RATIO也許從未被編譯器看見;也許在編譯器開始處理源碼之前它就被預處理器移走了。於是記號名稱ASPECT_RATIO有可能沒進入記號表(symbol table)內。當運用此常量但獲得編譯錯誤時,錯誤信息可能會提到1.653而不是ASPECT_RATIO;也可能ASPECT_RATIO在別的頭文件裡,為追蹤帶來麻煩。
解決之道:
const double AspectRatio = 1.653;
作為一個語言常量,AspectRatio肯定會被編譯器看到,當然就會進入記號表內。此外,使用常量可能比使用#define導致較小量的碼,因為預處理器"盲目地將宏名稱ASPECT_RATIO替換為1.653"可能導致目標碼出現多份1.653,而用常量AspectRatio絕不會出現相同情況。
class專屬常量:為了將常量的作用域限制於class內,你必須讓它成為class的一個成員;而為確保此常量至多只有一份實體,你必須讓它成為一個static成員:
class CostEstimate {
private:
static const double FudgeFactor;//staticclass常量聲明
... //位於頭文件內
};
const double //static class常量定義
CostEstimate::FudgeFactor = 1.35;//位於實現文件內
我們無法利用#define創建一個class專屬常量,因為#defines並不重視作用域。一旦宏被定義,它就在其後的編譯過程中有效(除非在某處被#undef)。這意味#defines不僅不能夠用來定義class專屬常量,也不能夠提供任何封裝性,也就是說沒有所謂private#define(概念上)這樣的東西;而const成員變量是可以被封裝的。
當class編譯期間需要一個class常量值,如類的數組聲明式中,編譯器堅持必須在編譯期間知道數組的大小,可采用“theenum hack”補償做法。其理論基礎是:"一個屬於枚舉類型(enumerated type)的數值可權充int被使用"。
class GamePlayer {
private:
enum { NumTurns = 5 }; //"the enum hack" - 令NumTurns成為5的一個記號名稱.
int scores[NumTurns]; //這就沒問題了.
...
};
enum hack的行為某方面說比較像 #define而不像const,例如取一個const的地址是合法的,但取一個enum的地址或#define的地址通常都不合法。如果你不想讓別人獲得一個pointer或reference指向你的某個整數常量,enum可以幫助你實現這個約束。enums和#defines一樣絕不會導致非必要的內存分配。
· 對於單純常量,最好以const對象或enums替換#defines。
另一個常見的#define誤用情況是以它實現宏(macros)。宏看起來像函數,但不會招致函數調用帶來的額外開銷。
//以a和b的較大值調用f
#defineCALL_WITH_MAX(a, b) f((a) > (b) ? (a) : (b))
int a = 5, b = 0;
CALL_WITH_MAX(++a, b); //比較前a自增1次,比較後因返回a再次自增,a被累加二次!
CALL_WITH_MAX(++a, b+10); //比較前a自增1次,比較後因返回b,a只被累加一次
記住為宏中的所有實參加上小括號;但以上例子所帶來的麻煩是“a的遞增次數竟取決於它被拿來和誰比較”。
用template inline函數可以獲得宏帶來的效率以及一般函數的所有可預料行為和類型安全性(type safety):
template<typenameT>
inlinevoid callWithMax(const T& a, const T& b) //template c++,采用pass by reference-to-const.
{
f(a > b ? a : b);
}
這個template產出一整群函數,每個函數都接受兩個同型對象,並以其中較大者調用f。這裡不需要在函數本體中為參數加上括號,也不需要操心參數被核算(求值)多次……等等。此外由於callWithMax是個真正的函數,它遵守作用域和訪問規則。例如你絕對可以寫出一個"class內的private inline函數"。一般而言宏無法完成此事。
有了consts、enums和inlines,我們對預處理器(特別是#define)的需求降低了,但並非完全消除。#include仍然是必需品,而#ifdef/#ifndef也繼續扮演控制編譯的重要角色。
· 對於形似函數的宏(macros),最好改用inline函數替換#defines。
摘自 pandawuwyj的專欄