[筆記]《Effective C++》第五章 Implementations
阿新 • • 發佈:2021-09-16
條款26:Postpone variabledefinitions as long as possible.
儘可能延後變數定義式的出現。這樣做可增加程式的清晰度並改善程式效率。
- 你不只應該延後變數的定義,直到非得使用該變數的前一刻為止,甚至應該嘗試延後這份定義直到能夠給它初值實參為止。
條款27:Minimize casting.
- 如果可以,儘量避免轉型,特別是在注重效率的程式碼中避免dynamic_casts。如果有個設計需要轉型動作,試著發展無需轉型的替代設計。
- 如果轉型是必要的,試著將它隱藏於某個函式背後。客戶隨後可以呼叫該函式,而不需將轉型放進他們自己的程式碼內。
- 寧可使用C++-style(新式)轉型
C++提供四種新式轉型:
-
const_cast通常被用來將物件的常量性轉除(cast away the constness)。
-
dynamic_cast主要用來執行“安全向下轉型”(safe downcasting),也就是用來決定某物件是否歸屬繼承體系中的某個型別。dynamic_cast的許多實現版本執行速度相當慢。
-
reinterpret_cast意圖執行低階轉型,實際動作(及結果)可能取決於編譯器,這也就表示它不可移植。
-
static_cast用來強迫隱式轉換(implicit conversions),例如將non-const物件轉為const物件(就像條款3所為),或將int轉為double等等。
-
轉型並不是什麼都不做,任何一個型別轉換(不論是通過轉型操作而進行的顯式轉換,或通過編譯器完成的隱式轉換)往往真的令編譯器編譯出執行期間執行的碼。
我們可以避免使用dynamic_cast,需要dynamic_cast通常是因為你想在一個你認定為derived class物件身上執行derived class操作函式,但你的手上卻只有一個“指向base”的pointer或reference,你只能靠它們來處理物件。有兩種一般性的做法避免這個問題:
- 第一,使用容器並在其中儲存直接指向derived class物件的指標(通常是智慧指標)。
- 另一種做法可讓你通過base class介面處理“所有可能之各種Window派生類”,那就是在base class內
條款28:Avoid returning "handles" to object internals.
避免返回handles(包括references、指標、迭代器)指向物件內部。
- 成員變數的封裝性最多隻等於“返回其reference”的函式的訪問級別。
- 如果const成員函式傳出一個reference,後者所指資料與物件自身有關聯,而它又被儲存於物件之外,那麼這個函式的呼叫者可以修改那筆資料。
條款29:Strive for exception-safe code.
- 異常安全函式(Exception-safe functions)即使發生異常也不會洩漏資源或允許任何資料結構敗壞。這樣的函式區分為三種可能的保證:基本型、強烈型、不拋異常型。
- “強烈保證”往往能夠以copy-and-swap實現出來,但“強烈保證”並非對所有函式都可實現或具備現實意義。
- 函式提供的“異常安全保證”通常最高只等於其所呼叫之各個函式的“異常安全保證”中的最弱者。
帶有異常安全性的函式在異常被丟擲時,需要做到:
- 不洩漏任何資源。
- 不允許資料敗壞。
三種保證:
- 基本承諾:如果異常被丟擲,程式內的任何事物仍然保持在有效狀態下。
- 強烈保證:如果異常被丟擲,程式狀態不改變。
- 不拋擲(nothrow)保證:承諾絕不丟擲異常,因為它們總是能夠完成它們原先承諾的功能。
copy-and-swap常被用來實現強烈保證,其原則為:
- 為你打算修改的物件(原件)做出一份副本,然後在那副本身上做一切必要修改。若有任何修改動作丟擲異常,原物件仍保持未改變狀態。待所有改變都成功後,再將修改過的那個副本和原物件在一個不丟擲異常的操作中置換(swap)。
條款30:Understand the ins and outs of inlining.
- inline函式背後的整體觀念是,將“對此函式的每一個呼叫”都以函式本體替換之。
- Inlining在大多數C++程式中是編譯期行為。
- inline只是對編譯器的一個申請,不是強制命令。
- 所有對virtual函式的呼叫(除非是最平淡無奇的)都會使inlining落空。因為virtual意味“等待,直到執行期才確定呼叫哪個函式”,而 inline意味“執行前,先將呼叫動作替換為被呼叫函式的本體”。
- 建構函式和解構函式往往是inlining 的糟糕候選人。
- 大部分偵錯程式面對inline函式都束手無策。你如何在一個並不存在的函式內設立斷點(breakpoint)?
條款31:Minimize compilation dependencies between files.
- 支援“編譯依存性最小化”的一般構想是:相依於宣告式,不要相依於定義式。
- 程式庫標頭檔案應該以“完全且僅有宣告式”(full and declaration-only forms)的形式存在。這種做法不論是否涉及templates都適用。
以“宣告的依存性”替換“定義的依存性”:
- 如果使用object references或 object pointers 可以完成任務,就不要使用objects。
- 如果能夠,儘量以class宣告式替換class定義式。
- 為宣告式和定義式提供不同的標頭檔案。