1. 程式人生 > >Effective C++ 要點整理(一)

Effective C++ 要點整理(一)

條款04:確定物件被使用前已被初始化

  • 物件的初始化動作何時一定會發生,何時不一定發生。最佳處理辦法是:永遠在使用物件之前先將它初始化。
  • 對於內建以型別以外的任何其他東西,初始化責任落在建構函式身上。規則:確保每一個建構函式都將物件的每一個成員初始化。該規則容易奉行,重點是不要混淆賦值和初始化的概念。
  • C++規定:物件的成員變數的初始化動作發生在進入建構函式本體之前。
  • 如果成員變數是const或references,它們就一定需要初值,不能被賦值。為了避免需要記住成員變數何時必須在成員初值列中初始化,何時不需要,最簡單的做法就是:總是使用成員初值列。這樣做有時候絕對必要,且往往比賦值更高效。
  • 若classes中擁有多個建構函式,每個建構函式有自己的成員初值列。多份成員初值列的存在就會導致不受歡迎的重複(在初值列內)。這種情況下可以合理在初值列中遺漏那些“賦值表現的像初始化一樣好”的成員變數,改用賦值操作,並將那些賦值操作移到某個函式(通常為private),供給所有建構函式呼叫。
  • C++有固定的“成員初始化次序”。base classes更早於其derived classes被初始化。class的成員變數總是以其宣告次序被初始化。
    牢記:
    為內建型物件進行手工初始化,因為C++不保證初始化它們。
    建構函式最後使用成員初值列,而不要在建構函式本體內使用賦值操作。初值列列出的成員變數,其排列次序應該和它們在class中的宣告次序相同。
    為免除“跨變異單元之初始化次序”問題,請以local static物件替換non-local static物件。

條款05:瞭解C++默默編寫並呼叫哪些函式

  • 如果沒有宣告,編譯器會為它宣告(編譯器版本)的一個copy解構函式,一個copy assignment建構函式。此外如果沒有宣告任何建構函式,編譯器也會為你宣告一個default(預設)建構函式。所有這些函式都是public且 是inline的。

條款07:為多型基類宣告virtual解構函式

C++曾明確指出,當派生類(derived class)物件經由一個基類(base class)指標被刪除,而該基類還帶著一個non-virtual解構函式,其結果未有定義-實際執行時通常發生的是物件的派生成分沒被銷燬。而其基類成分通常會被銷燬,於是造成一個詭異的“區域性銷燬”物件。這會造成資源洩露、在偵錯程式上浪費許多時間。 而消除這個問題的做法很簡單:給基類一個virtual解構函式,此後刪除派生類物件就會銷燬整個物件,包括所有的派生類成分。


  • 類有一個pure virtual函式,所以它是一個抽象class,又由於它有一個virtual解構函式,所以不惜擔心解構函式的問題。但是現在必須為pure virtual解構函式提供一份定義:
    - AWOV: :~AWOV( ) { } // pure virtual 解構函式的定義。

解構函式的運作方式:最深層的派生的那個class其解構函式最先被呼叫,然後是其每一個base class的解構函式被呼叫。

  • 任何類只要帶有virtual函式都幾乎確定應該也有一個virtual解構函式。

  • 如果class不含virtual函式,通常表示它並不想用作一個base class。

    牢記
    帶多型性質的base class應該宣告一個virtual解構函式。如果class帶有任何virtual函式,它就應該擁有一個virtual解構函式。
    類的設計目的如果不是base class使用,或不是為了具備多型性質,就不該宣告virtual解構函式。

  • 條款20 :寧以pass-by-reference-to-const替換pass-by-value

    bool validateStudent(const Student &s);
    1.傳遞引用效率高,沒有任何建構函式或者解構函式被呼叫, 因為沒有任何新物件被建立。
    2.以引用方式傳遞引數也可以避免物件切割問題,當一個派生類物件以值傳遞方式傳遞並被視為基類物件,基類的copy建構函式會被呼叫,而“造成此物件的行為像個派生類物件”的那些特化性質全被切割掉了,僅僅留下一個基類物件。因為正是基類建構函式建立了它。
    3.解決切割問題的方法,就是以常引用方式傳遞引數
    4.在C++編譯器的底層,引用往往以指標實現出來,因此pass by reference通常意味著真正傳遞的是指標。因此如果有物件屬於內建型別(例如int),值傳遞往往比引用傳遞效率高。該忠告也適用於STL迭代器和函式物件。這是“規則值改變取決於你使用哪一部分C++”的一個例子

    請牢記
    儘量以pass-by-reference-to-const替換pass-by-value。前者比較高效,可以避免物件切割問題。
    以上規則並不適用於內建型別以及STL的迭代器和函式物件,對它們而言,pass-by-value往往更適當。

    條款32:確定你的public繼承塑模出is-a關係

    “public繼承”意味is-a。適用於基類身上的每一件事情一定也適用於派生類身上,因為每一個派生類物件也都是一個基類物件。

    條款34:區分介面繼承和實現繼承


    • 成員函式的介面總是會被繼承。
    • 宣告一個純虛擬函式的目的是為了讓派生類只繼承函式介面。
    • 宣告非純函式的目的,是讓派生類繼承該函式的介面和預設實現。

    純虛擬函式必須在派生類中重新宣告的,但是他們也可以擁有自己的實現。

  • 如果成員函式是一個非虛擬函式,意味著它並不打算在派生類中有不同的行為。實際上一個非虛成員函式所表現的不變形凌駕於其特異性,因為它便是不論派生類變得多麼特異化,它的行為都不可以改變。

  • 宣告非虛擬函式的目的是為了讓派生類整合函式的介面及一份強制性實現。

    牢記
    介面繼承和實現繼承不同。在public繼承之下,派生類總是繼承基類的介面。
    純虛擬函式只具體制定介面繼承。
    非純虛擬函式具體制定介面繼承及預設實現繼承。
    非虛擬函式具體制定介面繼承以及強制性實現繼承。

  • 條款39明智而審慎地使用private繼承


    • 如果classes之間的繼承關係是private,編譯器不會自動將一個derived class物件轉換為一個base class物件。
    • 由private base繼承而來的所有成員,在derived class中都會變成private屬性,不管在base class中原本是protected或public屬性。
    • 如果讓派生類以private形式繼承基類,用意是為了採用基類內已經完善的某些特性,不是因為基類物件和派生類物件存在有任何觀念上的關係。
    • private繼承純粹知識一種實現技術-這就是為什麼繼承自一個private base class的每樣東西在你的class內部都是private,因為他們都是實現枝節而已。
    • private繼承主要用於“當一個意欲成為派生類者想訪問一個意欲成為基類的protected成分,或為了重新定義一個或多個virtual函式”。但是這時候兩個雷之間的概念關係其實是一種“根據某物實現出”的關係。
    牢記
    - private繼承意味著根據某物實現出。它通常比複合的級別低。但是當派生類需要訪問protected base class的成員,或需要重新定義繼承而來的virtual函式時,這麼設計也是合理的。
    - 和複合不同,private繼承可以造成empty base 最優化。這對致力於“物件尺寸最小化”的程式庫開發者而言,很重要。

    條款44:將與引數無關的程式碼抽離templates

    牢記
    Templates生成多個classes和多個函式,所以任何template程式碼都不該與某個造成膨脹的template引數產生相依關係。
    因非型別模板引數而造成的程式碼膨脹,往往可消除,做法是以函式引數或class成員變數替換template引數。
    因型別引數而早晨的程式碼膨脹,往往可以降低,做法是讓帶有完全相同二進位制表述的具現型別共享實現碼。

    條款53:不要輕視編譯器的警告

    牢記
    嚴肅對待編譯器發出的警告資訊。
    不要過度依賴編譯器的報警能力,因為不同的編譯器對待事情的態度不同。一旦移植到另一個編譯器上,原本依賴的警告資訊可能會消失。