- Effective C++
- 視 C++ 爲一個語言聯邦
- C
- object-oriented C++面向對象:封裝、繼承、多態
- Template C++泛型編程:包括TMP(Template Metaprogramming 模板元編程)
- STL:是一個Template程序庫,包括容器、迭代器、算法、函數對象
- 寧以編譯器替換預處理器
儘量以 `const`、`enum`、`inline` 替換 `#define`
-
- 對於單純常量,最好以const對象或enums替換#defines(宏定義出錯時,編譯器會給出替換之後的錯誤信息,避免因追蹤變量而浪費時間)
- 對於形似函數的宏,最好改用inline函數替換#defines(#define單純文本替換,函數語義不清晰,使用時多加小括號)
- 儘可能使用const
- const 寫在T之前和之後無所謂,寫在*之前表示指向常量的指針,寫在*之後表示指針常量。STL的迭代器相當於一個T*指針,const_iterator相當於 const T*
- 將某些東西聲明爲const可以幫助編譯器偵測出錯誤用法
使用const成員函數,以pass by reference-to-const方式傳遞對象,提升效率
- 兩個成員函數如果只是常量性不同,可以被重載。調用時區分const.
- const成員函數不能調用non-const成員函數,當non-const和const成員函數有着實質等價的實現時,在non-const中調用const版本可以複用代碼
- bitwise constness與logical constness:
bitwise constness:(C++編譯器認爲的cosntness)。const成員函數不改變對象的任意一個bit。bitwise constness允許發生此現象:當某個指針隸屬於對象,而指針所指物不隸屬於對象時,成員函數更改了指針所指物。
logical constness:一個const成員函數可以修改它所處理的對象的某些bits。
達到logical constness:可以通過mutable關鍵字實現,mutable修飾的變量,將永遠處於可變的狀態,即使在一個const函數中。mutable釋放掉non-static成員變量的bitwise constness約束
- 確定對象被使用前已經初始化
- array(來自c part of c++)不保證其內容被初始化,vector(來自stl part of c++)有此保證
- 確保每一個構造函數都將每一個成員初始化
- 對象的成員變量的初始化動作發生在這些成員的default構造函數被自動調用之時,即進入構造函數本體之前。在構造函數內爲成員變量賦值(xxx=y)的動作是賦值而不是初始化(相當於首先調default構造函數爲成員變量設初值,然後立刻爲它們賦予新值)
- 使用成員初值列(member initialization list)可以避免上述現象,初值列中針對各個成員變量而設的實參,被拿去作爲各成員變量之構造函數的實參,進行copy構造。對大多數類型而言,只進行一次copy構造比上述先初始化再用拷貝賦值運算符高效許多。對內置類型而言,二者成本相同,但爲了一致性,最好也採用initialization list的方式進行初始化
- 總是在初值列中列出所有成員變量,以免還得記住哪些成員變量可以無需初值。(如國內值類型成員在成員初值列遺漏了它,就沒有初值,引發不明確行爲)
- const或references成員變量,一定需要初值,不能賦值
- 爲內置型對象進行手工初始化,因爲c++編譯器不保證初始化它們。
- 成員初始化次序:
base classes更早於derived classes被初始化
class成員變量按照其聲明次序被初始化,即使它們在成員初值列中以不同的次序出現。
- local static對象與non-local static對象:
static對象不包括stack和heap-based對象。包括global對象,定義於namespace作用域內的對象,在classed內、在函數內、在file作用域內被聲明爲static的對象。
local static對象:函數內的static對象
non-local static對象:其他static對象
- 編譯單元:產出單一目標文件(.o)的源碼。基本上是單一源碼文件加上其所含入的頭文件。
問題:c++對“定義域不同編譯單元內的non-local static對象”的初始化次序無明確定義。如果某編譯單元內的某個non-local static對象的初始化動作使用了另一編譯單元內的某個non-local static對象,它所用到的這個對象可能尚未被初始化。
解決:用local static對象替換non-loacl static對象:將每個non-local static對象搬到自己的專屬函數內,這些函數返回一個reference指向它所含的對象,然後用戶調用這些函數,而不是直接指涉這些對象。
- 瞭解c++編譯器默默編寫並調用哪些函數
- 如果自己沒有聲明,編譯器就會爲它聲明一個copy構造函數、一個copy assignment操作符和一個析構函數。如果自己沒有聲明任何構造函數,編譯器會爲它聲明一個default構造函數(如果自己有聲明一個有參構造函數,則編譯器不會再產出一個無參構造函數)。唯有當這些函數被調用時,它們纔會被編譯器創建出來。
- 編譯器產出的析構函數是non-virtual的,除非該class的base class自身生命有virtual析構函數,此時這個函數的虛屬性主要來自與其base class。
- 編譯器產出的copy構造函數和copy assignment操作符只是單純地將來源對象的每一個 non-static成員變量拷貝到目標對象
問題:c++並不允許將reference改指不同對象。如果打算在一個”內含reference成員”或“內含const成員”的class內支持賦值操作,必須自己定義copy assignment操作符
-
- 如果某個base classses將copy assignment操作符聲明爲private,編譯器將拒絕爲其derived classed生成一個copy assignment操作符(因爲編譯器爲derived classes所生的copy assignment操作符想象中可以處理base class成分,但它們卻無法調用base class 的assignment成員函數)
6.若不想使用編譯器自動生成的函數,就要明確拒絕
- 所有編譯器產出的函數都是public類型的,爲阻止這些函數被創建出來,可將相應的成員函數聲明爲private並且不予實現。或在base class中聲明private的函數然後在derived class中private繼承。
- 爲多態基類聲明virtual析構函數
- 只有當class內至少一個virtual函數時,爲它聲明一個virtual析構函數
- 任何class只要帶有virtual函數都應該有一個virtual的析構函數(當derived class對象經由一個base class指針被刪除,而該base class帶着一個non-virtual析構函數,其結果未定義——實際執行時通常發生的是對象的derived成分沒被銷燬)
- 如果class無virtual函數(通常表示不意圖作爲一個基類),則不用virtual的析構函數(vptr指針會使對象體積增加,同類型(例如c++中 Point類,c中自定義的Point類型)不能再傳遞至其他語言所寫的函數,除非明確補償vptr)
- virtual函數的實現:
爲實現virtual函數,對象必須攜帶一些信息(主要用於在運行期決定哪一個virtual函數該被調用),這份信息有vptr(virtual table pointer)指針指出。vptr指向一個由函數指針構成的數組(vtbl virtual table),每個帶有virtual函數的class都有一個vtbl。當對象調用某一virtual函數,編譯器在該對象的vptr所指的那個vtbl中尋找適當的函數指針。
- 析構函數運作順序:最深層派生(most derived)的那個class其析構函數最先被調用,然後是其每一個baseclass的析構函數被調用
- STL容器的析構函數均是non-virtual,所以不能繼承包括STL容器在內的任一個帶有non-virtual析構函數的class
- pure virtual 析構函數:當希望擁有一個抽象class,但手上沒有合適的pure virtual函數是,可以爲該class聲明一個pure virtual析構函數。因爲該class有一個pure virtual函數,所以它是抽象class,又由於它有個virtual析構函數,所以不需擔心析構函數的問題。
8.別讓異常逃離析構函數
- 析構函數吐出異常,程序可能過早結束或引發不明確行爲。(假設對象含有多個元素,在析構第一個元素時發生異常,但其他9個元素也應該被銷燬(否則會內存泄露),在析構第二個元素時又發生了異常,兩個同時作用的異常導致不明確行爲)
- 當析構函數中調用可能引發異常的函數時,析構函數可以:
- 如果該函數拋出異常就結束程序,通常通過調用abort函數完成
try(可能引發異常得函數)
catch(…){
// 記錄信息之類的操作
std::abort();
}
- 吞下因函數調用失敗引發的異常 // 不推薦
在catch(…){//不調用abort}
- 重新設計可能拋出異常的函數,使調用者有機會對可能出現的問題做出反應。
如果客戶需要對某個操作函數運行期間拋出的異常做出反應,那麼class應該提供一個普通函數(而非在析構函數中)執行該操作。
9.絕不在構造和析構函數中調用virtual函數
- base class構造期間virtual函數絕不會下降到derived class階層(在derived class對象的base class構造(析構)期間,對象的類型是base class而不是derived class)
- 一旦derived class析構函數開始執行,對象內的dericed class成員變量便呈現未定義值
- 如果class有多個構造函數,每個都要執行某些相同的工作,可以將共同的初始化代碼放進一個初始化函數中
- 當希望在base class中構造時需要調用適當版本的函數時,可以在base class中調用non-virtual函數,然後要求derived class的構造函數傳遞必要信息給base class構造函數
10.令operator= 返回一個reference to *this
包括+=、-=、*=等操作符也是一樣,返回一個reference to *this 可以實現連續賦值
類名& operator=(const 類名& rhs){
return *this; //
}
11.在operator=中處理自我賦值
當
先進行證同測試 if(this == rhs) return *this;
- 類名& operator=(const 類名& rhs){
// 先記住原本的成員指針變量 //這樣在new失敗的情況下不會刪除原本的內存
// 令原先的指針變量 = new T( );
// delete 記住的
// return *this;
}
- copy and swap
類名& operator=(const 類名& rhs){
類名 temp(rhs);
swap(temp);
return *this;
}
12.複製對象時勿忘其每一個成分
- copying函數應該確保複製”對象內的所有成員變量”及“所有base class成分”(調用base class copying函數)
不要嘗試以某個copying函數實現另一個copying函數(即不能用operator=調用拷貝構造函數或者相反),應該將共同機能放進第三個函數中並由兩個copying函數共同調用。