【C++】Effective C++/More Effective c++

Effective C++
  1. 視 C++ 爲一個語言聯邦(C、Object-Oriented C++、Template C++、STL)
  2. 寧可以編譯器替換預處理器(儘量以 const、enum、inline 替換 #define)
  3. 儘可能使用 const
  4. 確定對象被使用前已先被初始化(構造時賦值(copy 構造函數)比 default 構造後賦值(copy assignment)效率高)
  5. 瞭解 C++ 默默編寫並調用哪些函數(編譯器暗自爲 class 創建 default 構造函數、copy 構造函數、copy assignment 操作符、析構函數)
  6. 若不想使用編譯器自動生成的函數,就應該明確拒絕(將不想使用的成員函數聲明爲 private,並且不予實現)
  7. 爲多態基類聲明 virtual 析構函數(如果 class 帶有任何 virtual 函數,它就應該擁有一個 virtual 析構函數)
  8. 別讓異常逃離析構函數(析構函數應該吞下不傳播異常,或者結束程序,而不是吐出異常;如果要處理異常應該在非析構的普通函數處理)
  9. 絕不在構造和析構過程中調用 virtual 函數(因爲這類調用從不下降至 derived class)
  10. 令 operator= 返回一個 reference to *this (用於連鎖賦值)
  11. 在 operator= 中處理 “自我賦值”
  12. 賦值對象時應確保複製 “對象內的所有成員變量” 及 “所有 base class 成分”(調用基類複製構造函數)
  13. 以對象管理資源(資源在構造函數獲得,在析構函數釋放,建議使用智能指針,資源取得時機便是初始化時機(Resource Acquisition Is Initialization,RAII))
  14. 在資源管理類中小心 copying 行爲(普遍的 RAII class copying 行爲是:抑制 copying、引用計數、深度拷貝、轉移底部資源擁有權(類似 auto_ptr))
  15. 在資源管理類中提供對原始資源(raw resources)的訪問(對原始資源的訪問可能經過顯式轉換或隱式轉換,一般而言顯示轉換比較安全,隱式轉換對客戶比較方便)
  16. 成對使用 new 和 delete 時要採取相同形式(new 中使用 [] 則 delete [],new 中不使用 [] 則 delete)
  17. 以獨立語句將 newed 對象存儲於(置入)智能指針(如果不這樣做,可能會因爲編譯器優化,導致難以察覺的資源泄漏)
  18. 讓接口容易被正確使用,不易被誤用(促進正常使用的辦法:接口的一致性、內置類型的行爲兼容;阻止誤用的辦法:建立新類型,限制類型上的操作,約束對象值、消除客戶的資源管理責任)
  19. 設計 class 猶如設計 type,需要考慮對象創建、銷燬、初始化、賦值、值傳遞、合法值、繼承關係、轉換、一般化等等。
  20. 寧以 pass-by-reference-to-const 替換 pass-by-value (前者通常更高效、避免切割問題(slicing problem),但不適用於內置類型、STL迭代器、函數對象)
  21. 必須返回對象時,別妄想返回其 reference(絕不返回 pointer 或 reference 指向一個 local stack 對象,或返回 reference 指向一個 heap-allocated 對象,或返回 pointer 或 reference 指向一個 local static 對象而有可能同時需要多個這樣的對象。)
  22. 將成員變量聲明爲 private(爲了封裝、一致性、對其讀寫精確控制等)
  23. 寧以 non-member、non-friend 替換 member 函數(可增加封裝性、包裹彈性(packaging flexibility)、機能擴充性)
  24. 若所有參數(包括被this指針所指的那個隱喻參數)皆須要類型轉換,請爲此採用 non-member 函數
  25. 考慮寫一個不拋異常的 swap 函數
  26. 儘可能延後變量定義式的出現時間(可增加程序清晰度並改善程序效率)
  27. 儘量少做轉型動作(舊式:(T)expression、T(expression);新式:const_cast(expression)、dynamic_cast(expression)、reinterpret_cast(expression)、static_cast(expression)、;儘量避免轉型、注重效率避免 dynamic_casts、儘量設計成無需轉型、可把轉型封裝成函數、寧可用新式轉型)
  28. 避免使用 handles(包括 引用、指針、迭代器)指向對象內部(以增加封裝性、使 const 成員函數的行爲更像 const、降低 “虛吊號碼牌”(dangling handles,如懸空指針等)的可能性)
  29. 爲 “異常安全” 而努力是值得的(異常安全函數(Exception-safe functions)即使發生異常也不會泄露資源或允許任何數據結構敗壞,分爲三種可能的保證:基本型、強列型、不拋異常型)
  30. 透徹瞭解 inlining 的裏裏外外(inlining 在大多數 C++ 程序中是編譯期的行爲;inline 函數是否真正 inline,取決於編譯器;大部分編譯器拒絕太過複雜(如帶有循環或遞歸)的函數 inlining,而所有對 virtual 函數的調用(除非是最平淡無奇的)也都會使 inlining 落空;inline 造成的代碼膨脹可能帶來效率損失;inline 函數無法隨着程序庫的升級而升級)
  31. 將文件間的編譯依存關係降至最低(如果使用 object references 或 object pointers 可以完成任務,就不要使用 objects;如果能夠,儘量以 class 聲明式替換 class 定義式;爲聲明式和定義式提供不同的頭文件)
  32. 確定你的 public 繼承塑模出 is-a(是一種)關係(適用於 base classes 身上的每一件事情一定適用於 derived classes 身上,因爲每一個 derived class 對象也都是一個 base class 對象)
  33. 避免遮掩繼承而來的名字(可使用 using 聲明式或轉交函數(forwarding functions)來讓被遮掩的名字再見天日)
  34. 區分接口繼承和實現繼承(在 public 繼承之下,derived classes 總是繼承 base class 的接口;pure virtual 函數只具體指定接口繼承;非純 impure virtual 函數具體指定接口繼承及缺省實現繼承;non-virtual 函數具體指定接口繼承以及強制性實現繼承)
  35. 考慮 virtual 函數以外的其他選擇(如 Template Method 設計模式的 non-virtual interface(NVI)手法,將 virtual 函數替換爲 “函數指針成員變量”,以 tr1::function 成員變量替換 virtual 函數,將繼承體系內的 virtual 函數替換爲另一個繼承體系內的 virtual 函數)
  36. 絕不重新定義繼承而來的 non-virtual 函數
  37. 絕不重新定義繼承而來的缺省參數值,因爲缺省參數值是靜態綁定(statically bound),而 virtual 函數卻是動態綁定(dynamically bound)
  38. 通過複合塑模 has-a(有一個)或 “根據某物實現出”(在應用域(application domain),複合意味 has-a(有一個);在實現域(implementation domain),複合意味着 is-implemented-in-terms-of(根據某物實現出))
  39. 明智而審慎地使用 private 繼承(private 繼承意味着 is-implemented-in-terms-of(根據某物實現出),儘可能使用複合,當 derived class 需要訪問 protected base class 的成員,或需要重新定義繼承而來的時候 virtual 函數,或需要 empty base 最優化時,才使用 private 繼承)
  40. 明智而審慎地使用多重繼承(多繼承比單一繼承複雜,可能導致新的歧義性,以及對 virtual 繼承的需要,但確有正當用途,如 “public 繼承某個 interface class” 和 “private 繼承某個協助實現的 class”;virtual 繼承可解決多繼承下菱形繼承的二義性問題,但會增加大小、速度、初始化及賦值的複雜度等等成本)
  41. 瞭解隱式接口和編譯期多態(class 和 templates 都支持接口(interfaces)和多態(polymorphism);class 的接口是以簽名爲中心的顯式的(explicit),多態則是通過 virtual 函數發生於運行期;template 的接口是奠基於有效表達式的隱式的(implicit),多態則是通過 template 具現化和函數重載解析(function overloading resolution)發生於編譯期)
  42. 瞭解 typename 的雙重意義(聲明 template 類型參數是,前綴關鍵字 class 和 typename 的意義完全相同;請使用關鍵字 typename 標識嵌套從屬類型名稱,但不得在基類列(base class lists)或成員初值列(member initialization list)內以它作爲 base class 修飾符)
  43. 學習處理模板化基類內的名稱(可在 derived class templates 內通過 this-> 指涉 base class templates 內的成員名稱,或藉由一個明白寫出的 “base class 資格修飾符” 完成)
  44. 將與參數無關的代碼抽離 templates(因類型模板參數(non-type template parameters)而造成代碼膨脹往往可以通過函數參數或 class 成員變量替換 template 參數來消除;因類型參數(type parameters)而造成的代碼膨脹往往可以通過讓帶有完全相同二進制表述(binary representations)的實現類型(instantiation types)共享實現碼)
  45. 運用成員函數模板接受所有兼容類型(請使用成員函數模板(member function templates)生成 “可接受所有兼容類型” 的函數;聲明 member templates 用於 “泛化 copy 構造” 或 “泛化 assignment 操作” 時還需要聲明正常的 copy 構造函數和 copy assignment 操作符)
  46. 需要類型轉換時請爲模板定義非成員函數(當我們編寫一個 class template,而它所提供之 “與此 template 相關的” 函數支持 “所有參數之隱式類型轉換” 時,請將那些函數定義爲 “class template 內部的 friend 函數”)
  47. 請使用 traits classes 表現類型信息(traits classes 通過 templates 和 “templates 特化” 使得 “類型相關信息” 在編譯期可用,通過重載技術(overloading)實現在編譯期對類型執行 if…else 測試)
  48. 認識 template 元編程(模板元編程(TMP,template metaprogramming)可將工作由運行期移往編譯期,因此得以實現早期錯誤偵測和更高的執行效率;TMP 可被用來生成 “給予政策選擇組合”(based on combinations of policy choices)的客戶定製代碼,也可用來避免生成對某些特殊類型並不適合的代碼)
  49. 瞭解 new-handler 的行爲(set_new_handler 允許客戶指定一個在內存分配無法獲得滿足時被調用的函數;nothrow new 是一個頗具侷限的工具,因爲它只適用於內存分配(operator new),後繼的構造函數調用還是可能拋出異常)
  50. 瞭解 new 和 delete 的合理替換時機(爲了檢測運用錯誤、收集動態分配內存之使用統計信息、增加分配和歸還速度、降低缺省內存管理器帶來的空間額外開銷、彌補缺省分配器中的非最佳齊位、將相關對象成簇集中、獲得非傳統的行爲)
  51. 編寫 new 和 delete 時需固守常規(operator new 應該內涵一個無窮循環,並在其中嘗試分配內存,如果它無法滿足內存需求,就應該調用 new-handler,它也應該有能力處理 0 bytes 申請,class 專屬版本則還應該處理 “比正確大小更大的(錯誤)申請”;operator delete 應該在收到 null 指針時不做任何事,class 專屬版本則還應該處理 “比正確大小更大的(錯誤)申請”)
  52. 寫了 placement new 也要寫 placement delete(當你寫一個 placement operator new,請確定也寫出了對應的 placement operator delete,否則可能會發生隱微而時斷時續的內存泄漏;當你聲明 placement new 和 placement delete,請確定不要無意識(非故意)地遮掩了它們地正常版本)
  53. 不要輕忽編譯器的警告
  54. 讓自己熟悉包括 TR1 在內的標準程序庫(TR1,C++ Technical Report 1,C++11 標準的草稿文件)
  55. 讓自己熟悉 Boost(準標準庫)
More Effective c++
  1. 仔細區別 pointers 和 references(當你知道你需要指向某個東西,而且絕不會改變指向其他東西,或是當你實現一個操作符而其語法需求無法由 pointers 達成,你就應該選擇 references;任何其他時候,請採用 pointers)
  2. 最好使用 C++ 轉型操作符(static_cast、const_cast、dynamic_cast、reinterpret_cast)
  3. 絕不要以多態(polymorphically)方式處理數組(多態(polymorphism)和指針算術不能混用;數組對象幾乎總是會涉及指針的算術運算,所以數組和多態不要混用)
  4. 非必要不提供 default constructor(避免對象中的字段被無意義地初始化)
  5. 對定製的 “類型轉換函數” 保持警覺(單自變量 constructors 可通過簡易法(explicit 關鍵字)或代理類(proxy classes)來避免編譯器誤用;隱式類型轉換操作符可改爲顯式的 member function 來避免非預期行爲)
  6. 區別 increment/decrement 操作符的前置(prefix)和後置(postfix)形式(前置式累加後取出,返回一個 reference;後置式取出後累加,返回一個 const 對象;處理用戶定製類型時,應該儘可能使用前置式 increment;後置式的實現應以其前置式兄弟爲基礎)
  7. 千萬不要重載 &&,|| 和 , 操作符(&& 與 || 的重載會用 “函數調用語義” 取代 “驟死式語義”;, 的重載導致不能保證左側表達式一定比右側表達式更早被評估)
  8. 瞭解各種不同意義的 new 和 delete(new operator、operator new、placement new、operator new[];delete operator、operator delete、destructor、operator delete[])
  9. 利用 destructors 避免泄漏資源(在 destructors 釋放資源可以避免異常時的資源泄漏)
  10. 在 constructors 內阻止資源泄漏(由於 C++ 只會析構已構造完成的對象,因此在構造函數可以使用 try…catch 或者 auto_ptr(以及與之相似的 classes) 處理異常時資源泄露問題)
  11. 禁止異常流出 destructors 之外(原因:一、避免 terminate 函數在 exception 傳播過程的棧展開(stack-unwinding)機制種被調用;二、協助確保 destructors 完成其應該完成的所有事情)
  12. 瞭解 “拋出一個 exception” 與 “傳遞一個參數” 或 “調用一個虛函數” 之間的差異(第一,exception objects 總是會被複制(by pointer 除外),如果以 by value 方式捕捉甚至被複制兩次,而傳遞給函數參數的對象則不一定得複製;第二,“被拋出成爲 exceptions” 的對象,其被允許的類型轉換動作比 “被傳遞到函數去” 的對象少;第三,catch 子句以其 “出現於源代碼的順序” 被編譯器檢驗對比,其中第一個匹配成功者便執行,而調用一個虛函數,被選中執行的是那個 “與對象類型最佳吻合” 的函數)
  13. 以 by reference 方式捕獲 exceptions(可避免對象刪除問題、exception objects 的切割問題,可保留捕捉標準 exceptions 的能力,可約束 exception object 需要複製的次數)
  14. 明智運用 exception specifications(exception specifications 對 “函數希望拋出什麼樣的 exceptions” 提供了卓越的說明;也有一些缺點,包括編譯器只對它們做局部性檢驗而很容易不經意地違反,與可能會妨礙更上層的 exception 處理函數處理未預期的 exceptions)
  15. 瞭解異常處理的成本(粗略估計,如果使用 try 語句塊,代碼大約整體膨脹 5%-10%,執行速度亦大約下降這個數;因此請將你對 try 語句塊和 exception specifications 的使用限制於非用不可的地點,並且在真正異常的情況下才拋出 exceptions)
  16. 謹記 80-20 法則(軟件的整體性能幾乎總是由其構成要素(代碼)的一小部分決定的,可使用程序分析器(program profiler)識別出消耗資源的代碼)
  17. 考慮使用 lazy evaluation(緩式評估)(可應用於:Reference Counting(引用計數)來避免非必要的對象複製、區分 operator[] 的讀和寫動作來做不同的事情、Lazy Fetching(緩式取出)來避免非必要的數據庫讀取動作、Lazy Expression Evaluation(表達式緩評估)來避免非必要的數值計算動作)
  18. 分期攤還預期的計算成本(當你必須支持某些運算而其結構幾乎總是被需要,或其結果常常被多次需要的時候,over-eager evaluation(超急評估)可以改善程序效率)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章