Effective C++

Effective C++

改善程序與設計的55個具體作法

讓本身習慣C++

視C++爲一個語言聯邦

  • C++是多重範式編程語言:
    • 過程式編程;
    • 面向對象編程;
    • 函數式編程;
    • 泛型編程;
    • 元編程(metaprogramming). --- 利用模板實現.
  • STL是一個template程序庫:
    • 容器(containers).
    • 迭代器(iterators).
    • 算法(algorithms).
    • 函數對象(function objects).

儘可能以const, enum, inline替換#define

  • 宏的預處理方式不太安全.

儘量地使用const

  • const指定一個語義約束, 並經過編譯器進行強制實施.

肯定對象被使用前已被初始化

  • 讀取未初始化的值會致使不明確的行爲.
  • 初始化列表應該與變量聲明次序保持一致.
  • 所謂static對象, 其壽命從被構造出來直到程序結束爲止.
  • 爲了不'跨編譯單元的初始化序列', 應該以局部static對象替換掉非局部的static對象.

構造, 析構, 賦值運算

瞭解C++默默編寫並調用那些函數

  • copy構造函數;
  • copy 賦值('=')操做符;
  • 默認的析構函數;
    • 編譯器產出的虛構函數是non-virtual.
  • 默認的構造函數, 這些函數都被設置爲inline和public.
  • 若是某個基類將copy assignment操做符聲明爲private, 編譯器將不會爲派生類生成一個賦值操做符.

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

  • 將複製構造函數和copy assignment操做符聲明爲private. 並且故意不實現它.
  • 爲多態基類聲明virtual析構函數, 設計工廠模式的實現常常用到.
  • virtual函數的目的是讓派生類的實現得以客製化.
  • 任何類(class)只要有virtual函數都應該有一個virtual的析構函數, 確保多態的正常運行.
    • 主要用來肯定運行期該執行那個virtual函數.
    • 當對象調用某個virtual函數時, 實際上被調用的函數取決於該對象的vptr(徐標指針)所指向的vptl(虛函數列表);
      • 編譯器會在vptl(虛函數列表)中尋找適當的函數指針.

不要讓異常逃離析構函數

  • 析構函數應該捕獲任何異常, 並再也不讓其進行傳播.
  • class應該提供處理異常的普通函數.

毫不在構造和析構函數中調用virtual函數

  • 在派生類的基類構造期間, 對象的類型是基類而不是派生類.
  • 這類函數調用並不能調用派生類的虛函數的多態.

令 operator=返回一個指向this指針的引用

  • 賦值操做符必須返回一個reference指向操做符的左側實參.

在 operator=中處理自我賦值

  • copy and swap技術. 值傳遞會形成複製副本.

複製對象時, 不要忘記其每個成分

  • 進行深拷貝.
  • copying全部的局部成員變量;
  • 調用適當的基類中的copy函數.
  • 不該該在copy assignment操做符中調用copy構造函數.

資源管理

  • C++ 程序中最多見的資源是動態分配的內存.
    • 文件描述符(file descriptors).
    • 互斥鎖(mutex locks).
    • 數據庫鏈接.
    • 網絡socket.

以對象管理資源

  • 得到資源後了當即放進管理對象內(managing object).
  • 資源取得時機即是初始化時機(Resource Acquisition Is Initialization, RAII).
  • 管理對象(managing object) 運用析構函數確保資源被釋放.

在資源管理中心小小copying行爲

  • 禁止複製, 許多時候容許RAII對象被複制是不合理的.
  • 對底層資源使用引用計數法(reference count).
  • 複製底部資源使用深度拷貝.
  • 轉移底部資源的擁有權(shared_ptr, weak_ptr, unique_ptr).

在資源內部中提供對原始資源的訪問

  • shared_ptr中有一個get的成員函數, 用來執行顯示轉換, 返回智能指針內部的原始指針.
  • shared_ptr將它全部的引用計數機構都封裝起來了.

成對使用new和delete時要採起相同形式

  • new不只要分配動態內存, 並且還要調用一個(或多個)構造函數.
  • 因此delete中也要求要執行對應的析構函數.
  • 儘可能最好不要對數組形式作typedefs動做.

以獨立語句將newed對象置入智能指針

  • 若是不這麼作, 一旦異常被拋出, 有可能致使難以察覺的資源泄漏.

設計與聲明

讓接口容易被正確使用, 不易被誤用

設計class猶如設計type

寧願以pass-by-reference-to-const替換pass-by-value(傳引用的方式替換值傳遞)

必需要返回對象時, 不要返回其reference

  • 這可能致使coredump, 由於內部對象離開做用域就會自動析構掉了.

將成員變量聲明爲private

寧以非成員, 非友函數替換掉成員函數

若全部參數皆須要類型轉換, 應該採用非成員函數

考慮寫一個不拋異常的swap函數

  • 調用swap時應針對std::swap使用using聲明式, 而後調用swap而且不帶任何命名空間資格修飾.

實現

  • 太快定義變量可能形成效率上的拖延;
  • 過分使用轉型(casts)可能致使代碼變慢又難維護, 還會招來微妙難解的錯誤;
  • 返回對象"內部數據的句柄(handles)";
  • 未考慮異常帶來的衝擊則可能致使資源泄漏和數據敗壞;
  • 過分熱心地inlining可能引發代碼膨脹;
  • 過分耦合(coupling)則可能致使讓人不滿意的冗長編譯時間(buid times).

儘量延後變量定義式的出現時間

儘可能少作類型轉換動做

  • C++四種新式轉換:
    • const_cast<T>(expression);
    • dynamic_cast<T>(expression);
    • reinterpret_cast<T>(expression);
    • static_cast<T>(expression);

避免返回handles()指向對象內部成分

爲異常安全而努力是值得的

  • 不泄漏任何資源, 不容許數據敗壞, 基本承諾, 強烈保證.

完全瞭解inlining的裏裏外外

  • 使用inline會增長目標碼的大小 ---> 進而致使換頁行爲 ---> 下降高速緩存的命中率 ---> 效率受損.
  • Template的具現化與inlining無關.
  • 80-20經驗法則, 80%的執行時間花費在20%的代碼上.
  • 不要由於函數模板(function template)出如今頭文件, 就將他們聲明爲inline.

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

  • 若是使用對象引用或對象指針能夠完成的任務, 就不要使用對象(objects)了.
  • 若是可以, 儘可能以class聲明式替換class定義式.
  • 爲聲明式和定義式提供不一樣的頭文件.

繼承與面向對象設計

  • 每一個繼承連接(link)能夠是public, protected, 或private.

肯定public繼承是is-a關係.

  • 世界上不存在一個適用於全部軟件的完美設計.
  • 好的接口能夠防止無效的代碼經過編譯.
  • public 繼承主張, 可以實施於基類身上的每件事情, 也能夠施行於派生類對象身上.
  • 類之間(classes)的關係還有:
    • has-a(有一個)的關係;
    • is-implemented-in-terms-of(根據某物實現出).

避免遮掩繼承而來的名稱

  • 內層做用域的名稱會遮掩外層做用域的名稱.
  • 派生類繼承了聲明於基類中的全部東西. --- 派生類的做用域被嵌套在基類的做用域內.
  • 派生類僅但願從新定義或覆寫基類中的一部分, 必須爲本來會被遮掩的每一個名稱引入一個using聲明式, 不然某些但願繼承的名稱會被遮掩.
  • private繼承中, 可能須要轉交函數(forwarding function).

區分接口繼承和實現繼承

  • 函數接口繼承(function interfaces); --- 純虛函數進行實現的.
  • 函數實現繼承(function implementations);
  • 純虛函數(pure virtual)函數的目的是爲了讓派生類只繼承函數接口.
  • pure virtual有兩個突出的特色:
    • 它們必須被繼承了的具象類從新聲明;
    • 並且它們在抽象類中沒有具體的實現.
  • 聲明一個簡樸的非純虛函數, 是讓派生類繼承該函數的接口和缺省實現.
  • 聲明非虛函數的目的是爲了令派生類繼承函數的接口及一份強制性實現.

考慮virtual函數之外的其餘選擇

  • 由非虛接口手法實現Template模式;
  • 由Function Pointers實現Strategy模式.
  • 用函數對象實現Strategy模式.

毫不從新定義繼承而來的非虛函數

  • 非虛函數是靜態綁定的, 而virtual函數倒是動態綁定的, 只用在運行的時候才知道該調用那個函數.(經過指針類型進行推斷).
  • 多態性(polymorphic)基類內的析構函數也應該是virtual(虛)的.

毫不從新定義繼承而來的缺省參數值

  • virtual函數系動態綁定(dynamically bound), 而缺省參數值倒是靜態綁定(statically bound).
  • 靜態綁定也叫前期綁定(early binding), 而動態綁定又叫後期綁定(late binding).
  • 函數調用的缺省參數值指調用前已將進行賦值(draw(ShapeColor color = Red);).

經過聚合/複合實現has-a關係或者'根據某物實現出'

  • 複合意味has-a(有一個)或is-implemented-in-terms-of(根據某物實現出).
  • is-implemented-in-terms-of(根據某物實現出)表現更多的是複用一個類.
  • 複用(composition)與public繼承徹底不一樣的意義.
  • 在應用域(application domain), 複合意味has-a(有一個).
  • 在實現域(implementation domain), 複合意味is-implemented-in-terms-of(根據某物實現出).

明智而審慎地使用private繼承

  • 若是兩個類之間的繼承關係是private, 編譯器不會自動將一個派生類對象轉換爲一個基類對象.
  • 由private基類繼承而來的全部成員, 在派生類中都會變成private屬性, 即便他們在基類中本來是public屬性和protected屬性.
  • private繼承意味着只有實現部分被繼承, 接口部分應省略掉.

明智而審慎地使用多重繼承

  • 單一繼承是好的, 但多重繼承不值得擁有(或使用).
  • 多重繼承的意思是繼承一個以上的基類, 但這些基類並不常在繼承體系中又有更高級的基類, 可能會致使鑽石型多重繼承.

模板與泛型編程

  • C++ template機制徹底是一部完整的圖靈機(turing-complete): 能夠被用來計算任何可計算的值.

瞭解隱式接口和編譯期多態

  • 面向對象編程以顯示接口(explicit interfaces)和運行期多態(runtime polymorphism)解決問題.
  • 對類而言, 接口是顯式的(explicit), 以函數簽名爲中心; 多態經過virtual(虛)函數發生於運行期.
  • 對template參數而言, 接口是隱式的(implicit), 基於有效表達式; 多態則是經過template具現化和函數重載解析(function overloading resolution)發生於編譯期.

瞭解typename的雙重意義

  • typename暗示參數並不是必定得是個class類型.
  • template內出現的名稱若是相依於某個template參數, 稱之爲從屬名稱(dependent names).
  • 任什麼時候候當想要在template中涉及一個嵌套從屬類型名稱, 就必須在前一個位置放上關鍵字typename.
  • 但不能在基類列表或成員初值列表內做爲基類的修飾符.

學習處理模板化基類內的名稱

  • 對編譯器承若基類模板的任何特化版本都將支持其通常(泛化)版本所提供的接口.
  • 可在派生類模板內經過this指針, 指涉基類模板內的成員名稱.或寫基類資格修飾符完成.

將與參數無關的代碼抽離templates

  • 任何template代碼都不該該與狗哥形成膨脹的template參數相依關係.

運行成員函數模板接受全部兼容類型

  • 使用成員函數模板(member function templates)生成可接受全部兼容類型的函數.
  • 聲明member templates用於泛化copy構造或泛化assignment操做, 不如聲明正常的構造函數和copy assignment操做符.

須要類型轉換時請爲模板定義非成員函數

使用trait classes表現類型信息

認識template元編程

定製new和delete

  • heap是一個可被改動的全局資源.

瞭解new-handler的行爲

  • 讓更多內存可被使用.
  • 安裝另外一個new-handler.
  • 卸除new-handler.
  • 拋出bad_alloc(或派生自bad_alloc)的異常.
  • 不返回(一般調用abort或exit).

瞭解new和delete的合理替換時機

  • 用來檢測運用上的錯誤.
  • 爲了強化效果.
  • 爲了手機使用上的統計數據.

編寫new和delete是須要固守常規

寫了placement new也要寫placement delete

雜項討論

不要輕視編譯器的警告

熟悉包括TR1在內的標準程序庫

熟悉Boost

相關文章
相關標籤/搜索