Effective C++ 筆記 part 1、2

  • Effective C++
  1. 視 C++ 爲一個語言聯邦
    1. C
    2. object-oriented C++面向對象:封裝、繼承、多態
    3. Template C++泛型編程:包括TMP(Template Metaprogramming 模板元編程)
    4. STL:是一個Template程序庫,包括容器、迭代器、算法、函數對象
  2. 寧以編譯器替換預處理器

儘量以 `const`、`enum`、`inline` 替換 `#define`

    1. 對於單純常量,最好以const對象或enums替換#defines(宏定義出錯時,編譯器會給出替換之後的錯誤信息,避免因追蹤變量而浪費時間)
    2. 對於形似函數的宏,最好改用inline函數替換#defines(#define單純文本替換,函數語義不清晰,使用時多加小括號)
  1. 儘可能使用const
  1. const 寫在T之前和之後無所謂,寫在*之前表示指向常量的指針,寫在*之後表示指針常量。STL的迭代器相當於一個T*指針,const_iterator相當於 const T*
  2. 將某些東西聲明爲const可以幫助編譯器偵測出錯誤用法

使用const成員函數,以pass by reference-to-const方式傳遞對象,提升效率

  1. 兩個成員函數如果只是常量性不同,可以被重載。調用時區分const.
  2. const成員函數不能調用non-const成員函數,當non-const和const成員函數有着實質等價的實現時,在non-const中調用const版本可以複用代碼
  3. 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約束

  1. 確定對象被使用前已經初始化
  1. array(來自c part of c++)不保證其內容被初始化,vector(來自stl part of c++)有此保證
  2. 確保每一個構造函數都將每一個成員初始化
    1. 對象的成員變量的初始化動作發生在這些成員的default構造函數被自動調用之時,即進入構造函數本體之前。在構造函數內爲成員變量賦值(xxx=y)的動作是賦值而不是初始化(相當於首先調default構造函數爲成員變量設初值,然後立刻爲它們賦予新值)
    2. 使用成員初值列(member initialization list)可以避免上述現象,初值列中針對各個成員變量而設的實參,被拿去作爲各成員變量之構造函數的實參,進行copy構造。對大多數類型而言,只進行一次copy構造比上述先初始化再用拷貝賦值運算符高效許多。對內置類型而言,二者成本相同,但爲了一致性,最好也採用initialization list的方式進行初始化
    3. 總是在初值列中列出所有成員變量,以免還得記住哪些成員變量可以無需初值。(如國內值類型成員在成員初值列遺漏了它,就沒有初值,引發不明確行爲)
    4. const或references成員變量,一定需要初值,不能賦值
    5. 爲內置型對象進行手工初始化,因爲c++編譯器不保證初始化它們。
  3. 成員初始化次序:

base classes更早於derived classes被初始化

class成員變量按照其聲明次序被初始化,即使它們在成員初值列中以不同的次序出現。

  1. local static對象與non-local static對象:

static對象不包括stack和heap-based對象。包括global對象,定義於namespace作用域內的對象,在classed內、在函數內、在file作用域內被聲明爲static的對象。

local static對象:函數內的static對象

non-local static對象:其他static對象

  1. 編譯單元:產出單一目標文件(.o)的源碼。基本上是單一源碼文件加上其所含入的頭文件。

問題:c++對“定義域不同編譯單元內的non-local static對象”的初始化次序無明確定義。如果某編譯單元內的某個non-local static對象的初始化動作使用了另一編譯單元內的某個non-local static對象,它所用到的這個對象可能尚未被初始化。

解決:用local static對象替換non-loacl static對象:將每個non-local static對象搬到自己的專屬函數內,這些函數返回一個reference指向它所含的對象,然後用戶調用這些函數,而不是直接指涉這些對象。

  1. 瞭解c++編譯器默默編寫並調用哪些函數
  1. 如果自己沒有聲明,編譯器就會爲它聲明一個copy構造函數、一個copy assignment操作符和一個析構函數。如果自己沒有聲明任何構造函數,編譯器會爲它聲明一個default構造函數(如果自己有聲明一個有參構造函數,則編譯器不會再產出一個無參構造函數)。唯有當這些函數被調用時,它們纔會被編譯器創建出來。
    1. 編譯器產出的析構函數是non-virtual的,除非該class的base class自身生命有virtual析構函數,此時這個函數的虛屬性主要來自與其base class。
    2. 編譯器產出的copy構造函數和copy assignment操作符只是單純地將來源對象的每一個 non-static成員變量拷貝到目標對象

問題:c++並不允許將reference改指不同對象。如果打算在一個”內含reference成員”或“內含const成員”的class內支持賦值操作,必須自己定義copy assignment操作符

    1. 如果某個base classses將copy assignment操作符聲明爲private,編譯器將拒絕爲其derived classed生成一個copy assignment操作符(因爲編譯器爲derived classes所生的copy assignment操作符想象中可以處理base class成分,但它們卻無法調用base class 的assignment成員函數)

6.若不想使用編譯器自動生成的函數,就要明確拒絕

  1. 所有編譯器產出的函數都是public類型的,爲阻止這些函數被創建出來,可將相應的成員函數聲明爲private並且不予實現。或在base class中聲明private的函數然後在derived class中private繼承。
  1. 爲多態基類聲明virtual析構函數
  1. 只有當class內至少一個virtual函數時,爲它聲明一個virtual析構函數
    1. 任何class只要帶有virtual函數都應該有一個virtual的析構函數(derived class對象經由一個base class指針被刪除,而該base class帶着一個non-virtual析構函數,其結果未定義——實際執行時通常發生的是對象的derived成分沒被銷燬)
    2. 如果class無virtual函數(通常表示不意圖作爲一個基類),則不用virtual的析構函數(vptr指針會使對象體積增加,同類型(例如c++中 Point類,c中自定義的Point類型)不能再傳遞至其他語言所寫的函數,除非明確補償vptr)
  2. virtual函數的實現:

爲實現virtual函數,對象必須攜帶一些信息(主要用於在運行期決定哪一個virtual函數該被調用),這份信息有vptr(virtual table pointer)指針指出。vptr指向一個由函數指針構成的數組(vtbl virtual table),每個帶有virtual函數的class都有一個vtbl。當對象調用某一virtual函數,編譯器在該對象的vptr所指的那個vtbl中尋找適當的函數指針。

  1. 析構函數運作順序:最深層派生(most derived)的那個class其析構函數最先被調用,然後是其每一個baseclass的析構函數被調用
  2. STL容器的析構函數均是non-virtual,所以不能繼承包括STL容器在內的任一個帶有non-virtual析構函數的class
  3. pure virtual 析構函數:當希望擁有一個抽象class,但手上沒有合適的pure virtual函數是,可以爲該class聲明一個pure virtual析構函數。因爲該class有一個pure virtual函數,所以它是抽象class,又由於它有個virtual析構函數,所以不需擔心析構函數的問題。

8.別讓異常逃離析構函數

  1. 析構函數吐出異常,程序可能過早結束或引發不明確行爲。(假設對象含有多個元素,在析構第一個元素時發生異常,但其他9個元素也應該被銷燬(否則會內存泄露),在析構第二個元素時又發生了異常,兩個同時作用的異常導致不明確行爲)
  2. 當析構函數中調用可能引發異常的函數時,析構函數可以:
    1. 如果該函數拋出異常就結束程序,通常通過調用abort函數完成

try(可能引發異常得函數)

catch(…){

        // 記錄信息之類的操作

        std::abort();

}

  1. 吞下因函數調用失敗引發的異常  // 不推薦

在catch(…){//不調用abort}

  1. 重新設計可能拋出異常的函數,使調用者有機會對可能出現的問題做出反應。

如果客戶需要對某個操作函數運行期間拋出的異常做出反應,那麼class應該提供一個普通函數(而非在析構函數中)執行該操作。

9.絕不在構造和析構函數中調用virtual函數

  1. base class構造期間virtual函數絕不會下降到derived class階層(在derived class對象的base class構造(析構)期間,對象的類型是base class而不是derived class)
  2. 一旦derived class析構函數開始執行,對象內的dericed class成員變量便呈現未定義值
  3. 如果class有多個構造函數,每個都要執行某些相同的工作,可以將共同的初始化代碼放進一個初始化函數中
  4. 當希望在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;

  1. 類名& operator=(const 類名& rhs){

       // 先記住原本的成員指針變量   //這樣在new失敗的情況下不會刪除原本的內存

       // 令原先的指針變量 = new T( );

       //  delete 記住的                

       // return *this;

}

  1. copy and swap

類名& operator=(const 類名& rhs){

       類名 temp(rhs);

       swap(temp);

       return *this;

}

12.複製對象時勿忘其每一個成分

  1. copying函數應該確保複製”對象內的所有成員變量”及“所有base class成分”(調用base class copying函數)

不要嘗試以某個copying函數實現另一個copying函數(即不能用operator=調用拷貝構造函數或者相反),應該將共同機能放進第三個函數中並由兩個copying函數共同調用。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章