Effective cpp 讀書筆記6

實現

26.儘可能延後變量定義時的出現時間

  1. 你不只應該延後變量定義,直到非得使用該變量的前一刻爲止,甚至應該嘗試延後這份定義直到能夠給它初值實參爲止

  2. 考慮一個經常出現的問題(如下):如果classes的一個賦值成本低於一組構造+析構成本,做法A大體而言比較高效。尤其當n很大的時候。否則做法B比較好。此外,做法A造成名稱w的作用於比做法B大,有事對程序的可理解性和易維護性造成衝突

// 方法A
Widget w;
for (int i = 0 ; i < n; i++) {
    w = xxx;
}

// 方法B
for (int i = 0 ; i < n ; i++) {
    Widget w(xxx);
}

27. 儘量少做轉型動作

  1. 如果可以,儘量避免轉型,特別是在注重效率的代碼中避免dynamic_cast。如果有一個設計需要轉型動作,試着發展無須轉型的替代設計

  2. 如果轉型是必要的,試着將它隱藏域某個函數背後。客戶隨後可以調用該函數,而不需將轉型放進它們自己的代碼

  3. 寧可使用C++-style轉型,不要使用舊式轉型。前者容易辨識出來,有分門別類的職掌

    • 轉型會破壞類型系統。所以乾淨通過編譯,是指它不企圖在任何對象身上執行任何不安全、無意義、愚蠢荒謬的操作
    • 四種新型轉型(如果需要轉型,請始終使用新式轉型)
      – const_cast:通常被用來將對象的常量性轉除
      – dynamic_cast:主要用來執行“安全向下轉型”(基類轉派生類),這是在運行期決定轉型的操作,可能耗費重大的運行成本
      – reinterpret_cast:執行低級轉型,實際動作(及結果)可能取決於編譯器,這兒就表示它不可移植
      – static_cast:強迫隱式轉換
    • 單個對象可能擁有一個以上的地址(例如“以Base*指向它”時的地址和“以Derived*指向它”時的地址)。這是C++可能會發生的,而實際上,一旦使用多重繼承,這種事幾乎以致發生。這是因爲,轉型的時候會存在一個偏移量,這個量與對象的佈局方式和它們的地址計算方式有關,而這兩樣東西有何編譯器相關
    • 因爲dynamic_cast是運行時的操作,注意繼承情況下,虛函數內的dynamic_cast,倘若虛函數存在嵌套調用,可能導致多次dynamic_cast,增加運行時間
    • dynamic_cast通常運用場景:你想在一個你認定爲派生類的對象上執行派生類的操作,但現在只有指向base的指針或引用。兩個一般性做法可以避免這種情況:
      – 使用容器並在其中存執直接指向派生類的指針
      – 在base class內提供virtual函數做你想對各個派生類做的事,即使有些派生類用不上
    • 個人理解:C++的轉型會破壞類型系統,帶來不必要的錯誤,C++需要儘量避免轉型,實在無法避免,則用新的轉型方法轉型,它們自帶異常檢測

28.避免返回handles指向對象內部成分

  1. 遵循這條款可增加封裝性,幫助const成員函數的行爲像個const,並將發生“虛吊號碼牌”的可能性降至最低

    • handle:reference、指針和迭代器統稱handles(號碼牌)
    • 成員變量的封裝性最多隻等於“返回其reference的函數的訪問級別”
    • 如果const成員函數傳出一個reference,後者所指數據和對象自身有關聯,而它又被存儲在對象外,那麼這個函數的調用者可以修改那個數據
    • 除了數據,私有或者protected的函數也屬於內部信息,不要返回它們的handles
    • 空懸的號碼牌:返回了handles,但因爲某些情況,所指對象不存在
  2. 個人理解:這一條主要是分析封裝型的,意思是如果你的成員函數能夠返回非const的指針或者reference指向私有函數,其實破壞了封裝性;儘量不要這樣,但不是絕對


29.爲“異常安全”而努力使值得的

  1. 異常安全函數即使發生異常也不會泄露資源或者允許任何數據結構敗壞。這樣的函數區分爲三種可能的保證:基本型、強烈型、不拋異常型

  2. “強烈保證”往往能夠以copy-and-swap實現,但“強烈保證”並非對所有函數都可實現

  3. 函數提供的“異常安全保證”通常最高只等於其所調用的各個函數的“異常安全保證”的最弱者

    • 異常安全性是指,在異常被拋出時滿足:不泄露任何資源;不允許數據敗壞
    • 對於泄露資源,採用智能指針解決
    • 基本型(基本承諾):如果異常被拋出,程序內任何事物仍保持在有效
    • 強烈保證:異常被拋出,程序狀態不改變
    • 不拋出異常:總能夠完成原先承諾的功能
    • int doSomething() throw(),注意,這句的意思不是不拋出異常,而是如果拋出,將是嚴重錯誤
    • 不要爲了表示某件事情發生而改變對象狀態,除非它真的發生了
  4. 個人理解:寫代碼的時候,需要對函數考慮異常安全性,首先通過智能指針防止資源泄漏,其次根據情況選擇所需的安全性級別,應該挑選“現實可實施”條件下的最強烈等級,但總體而言,我們的抉擇往往落在基本保證和強烈保證之間


30.透徹瞭解“inlining”的裏裏外外

  1. 將大多數inlining限制在小型、被頻繁調用的函數身上。這可使日後的調試過程和二進制升級更容易,也可使潛在的代碼膨脹問題最小化,使程序速度提升機會最大化

  2. 不要只因爲function Template出現在頭文件就將它們聲明爲inline

    • inlining好處:比宏好得多,可以調用它們而不需受函數調用帶來的額外開銷;編譯器最優化機制通常被設計用來濃縮“不含函數調用”的代碼,inline某個函數,或許編譯器有能力對它的執行語境相關最優化
    • inlining壞處:它的觀念是“對此函數的每個調用”都用函數本體替代,在編譯過程完成,將增加你的目標碼大小;如果函數庫內有一個inline函數,一旦改變,所有用到該函數的客戶端都必須重新編譯
    • inline只是對編譯器的申請,不是強制命令。申請可以隱喻提出也可以明確提出。隱喻方式是講函數定義在class定義式內(也就是一般寫頭文件的時候,直接在頭文件內寫函數體就是隱喻)
    • lnline函數是否inline取決於你的建置環境,主要取決於編譯器。因爲inline只是申請,但假如它們無法將你要求的函數inline,編譯器一般會給你警告信息
    • 不inline的情況:函數太過複雜;函數是virtual;通過函數指針進行調用的地方;構造函數和析構函數(即使是空的,但是C++內部會對這兩個函數做基本的操作,本身就帶有大量代碼)

31.將文件間的編譯依存關係降至最低

  1. 支持“編譯依存性最小化”的一般構想:相依於聲明式,不要相依於定義式。基於此構想的兩個手段是Handle classes 和 Interface classes

  2. 程序庫頭文件應該以“完全且僅有聲明式”的形式存在。這種做法不論是否涉及templates都適用

    • 一般寫代碼,頭文件中包含其他的頭文件,當其中一個頭文件改變的時候,包含它的所有文件全部都要重新編譯,也就是編譯依存關係較強
    • 所謂相依於聲明式不是定義式,本質是讓頭文件儘可能自我滿足。
    • 簡單的,意思是頭文件裏面,只是申明要用到的class,不要去include它,在定義的cpp文件裏面include,然後,使用的時候儘量用reference和pointer去解決,因爲定義這些handler的時候,只需要知道聲明而不需要定義類型;其次,定義複雜class的時候,寫3個文件,一個寫依賴的class的聲明式,另外是標準的h和cpp文件。這樣,至少保證類間的功能外殼互不相關
    • 然後是2種手段:
      – handle class:將實現的類分成兩個類,一個(handle class)只有若干功能外殼和實際實現這些功能的類的指針;一個是實現這些功能的類(Impl class),handle class的每個函數都調用對應的Impl class的功能函數。這樣實現和聲明就分開了,當修改了某個頭文件,只有handle的實現類需要重新編譯(只有它include)
      – Interface class:其實就是工廠模式,實現好ABC,實現對應的派生類,實現工廠生成各種派生類並調用
      – 缺陷:這兩種手段都有不足。Handle class必須實現指針,每次調用函數還包含一次指針調用函數,增加訪問的間接性;Interface class因爲有abc,所以很多virtual函數,除了增加內存(虛表),還增加一個跳躍
  3. 個人理解:這種手段不可能完全使得編譯依存變無。而且,當它們導致速度或大小差異過於重大以至於classes之間的耦合不成爲關鍵時,就用具象類替換它們

發佈了33 篇原創文章 · 獲贊 21 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章