總結: 囫圇吞棗地看完了EffectiveC++這本書,收獲不少。收獲主要是以下幾個方面:
編程注意細節 提高程序效率的細節比如const
is-a 和 has-a的區別pure-virtual、impure-virtual和non-virtual之間繼承該如何處理繼承中隱藏基類的成員等設計方法 什么時候該繼承(is-a),什么時候該以對象作為data member(is-inplemented-in-terms-of);Template method、 Strategy, 抽象工廠等設計模式泛型編程與模板編程異常安全性 異常處理copy-swap技術由于沒有太多的實戰經歷,所以有些東西并不是很懂。 所幸在看書過程中,做過一些筆記整理,其中 大部分內容摘抄自書上,一小部分加上自己的一點點理解,方便日后回顧。
02: 對于單純常量,最好以const
對象或 enums
替換#define
03: 盡可能使用const
mutable
關鍵字表征 const
成員函數可以修改mutable
成員04: 為內置對象進行手工初始化,C++并不保證初始化他們;
05: 在類中,如果有引用 或const對象,記得要自己編寫構造函數,拷貝構造函數等,因為編譯器自己生成的相關函數很大可能達不到要求。
06: 為了駁回編譯器自動提供的功能(構造函數,拷貝等),可以將相應的member function declared PRivate,并且不予實現; 或者使用像Uncopyable這樣的base class;
07: 當類作為基類的時候,有可能表現出多態的時候,應將虛構函數設計成virtual
09: 絕不在構造函數和析構函數中調用virtual function; 如果在base class 調用了virtual function,在派生類的構造函數初始化基類的時候,實際上還是調用的是base class的function,并沒有調用dervied class的內容(此時derived class 并沒有完成初始化)
10: 令 Operator= 返回一個referece to *this (方便連續賦值 如 a = b = c;);
11: 在operator=中處理“自我賦值”copy and swap 技術實現自我賦值;
class Widgt {void swap(Widgt& rhs);// 交換*this 和 rhs的數據}Widget& Widget::operator=(const Widget& rhs) { Widget tmp(rhs); swap(rhs); return *this;}12: 復制對象的時候務必記住每一個成員 copying函數應該確保復制“對象內的所有成員變量” 以及“所有base Class 的成分”
13: 以對象管理資源 記得用智能指針管理資源;
14: 在資源管理類中心小心copying行為
當資源管理類需要copy的時候 需要從以下幾個方面考慮 禁止復制(將拷貝構造函數,以及拷貝賦值函數 設置于private有點類似于unique_ptr<>)對底層資源使用“引用計數法” 模仿“shared_ptr”復制底部資源(深拷貝,每一份對象都有自己的heap內存)轉移資源的管理權(有點像 auto_ptr )15: 資源管理類中需要提供對原始資源的訪問
智能指針類可以隱式轉化為底部的原始指針,便于訪問底部的成員可以有顯式轉換(較為安全)和隱式轉換(對客戶比較安全)16: 成對使用new 和delete 尤其注意在利用typedef中聲明新的類型名時(盡量少用typedef聲明數組類型)
17: 以獨立語句將new出來的對象置入智能指針25 : Consider support for a non-throwing swap( 不拋異常)
缺省版本swap如果缺省版本的效率不夠,可以嘗試做
提供一個public swap函數(這個函數,不要拋出異常)在class 或template所在的命名空間內提供一個non-member swap,并調用上述的swap成員函數如果正編寫一個class(非template),需要特化std::swap, 并令他調用你的成員swap注意: 不要忘std里加入任何新的東西
// default swaptemplate <typename T>void swap(T& a, T& b){ T temp(a); a = b; b = tmp;}class Widget { public: void swap(Widget &other);}26: 盡可能延后變量定義式的出現的時間(用到的時候才再去聲明) 循環里用到的變量盡量在循環里聲明,除非 賦值成本比構造加析構低,處理代碼中效率高度敏感的部分27: 盡量少做轉型動作(如果轉型必要,就封裝在函數里面)(新式轉型 好于 舊式轉型)
const_castdynamic_caststatic_cast reinterpret_cast 不適合移植28: Avoid returning “handles”to object internals
handles (pointer, reference, iterators)29: Strive for exception safety code(為異常安全性努力是值得的)
帶有異常安全性的函數會: 不泄露任何資源不允許數據敗壞異常安全函數(Exception-safe code)提供下面三個保證之一
基本承諾
程序內的任何事物都保持在有效狀態下(對象或者數據結構不會有因此而損壞)當異常時,可以選用默認狀態,也可以回復到變換之前的狀態強烈保證(往往以copy-and-swap實現出來)
函數成功,就完全成功,如果失敗,程序會回復到調用函數之前的狀態nothrow 保證30: Understand the ins and outs of inlining
virtual 和inline同時出現,編譯器往往會拒絕inlineinline限制在小型、頻繁調用的函數身上構造函數最好不要inline31: 將文件間的編譯依存關系降至最低
接口與實現的分離 如果使用object reference 或者 object pointers可以完成任務,就不要使用objects如果能夠,盡量以class聲明式替換定義式為聲明式和定義式提供不同的頭文件32: 確定public繼承是is-a的關系(注意區分has-a)
適用于derived class身上的所有事情都能適用于base class身上33: Avoid hiding inherited names(避免隱藏繼承的成員名)
避免隱藏基類的成員名 可以使用using 聲明式或者 forwarding function34: Differentiate between inheritance of interface and inheritance of implementation(區分接口繼承和實現繼承)
pure virtual: 為了讓derived class繼承函數接口(繼承了的具象類都要重新聲明)impure virtual: 繼承該函數的接口和缺省實現non-virtual:為了令derived classes繼承函數的接口及一份強制性實現35: 考慮virtual函數以外的其他設計方式(提及設計模式中的Template Method 和 Strategy)
令客戶通過public non-virtual成員函數間接調用private函數(NVI手法)/// Template Methodclass GameCharacter {public: int healthValue() const { ··· // 做一些事前工作 int retVal = doHealthValue(); ··· //做一些事后工作 return value; } ···private: virtual int doHealthValue() const { ··· // 缺省算法,計算健康指數 } }借由function pointers實現strategy模式class GameCharacter;int defaultHealthCalc(const GameCharacter& gc);class GameCharacter {public: typedef std::function<int (const GameCharacter&)> HealthCalcFunc; explicit GameCharacter(HealthCalcFunc hcf = defaultHealthCalc) : healthFunc(hcf) {} int healthValue() const { return healthFunc(*this);}private: HealthCalcFunc healthFunc;};/// 可以轉換成function對象short calcHealth(const GameCharacter&); // 返回類型 non-intstruct HealthCalculator { //為計算健康值設計的函數對象 int operator()(const GameCharacter&) const {···}};class GameLevel {public: float health(const GameCharacter&) const; // 成員函數 ···};使用non-virtual interface手法,那是Template Method設計模式的一種特殊形式,它以public non-virtual成員函數包裹性較低訪問性(private或protected)的virtual函數將virtual函數替換為“函數指針成員變量”,這是strategy設計模式的一種分解表現形式以function成員變量替換virtual函數, 因此允許使用任何可以轉變成(callable entity)function對象的表達式,也是strategy的一種表現形式36: Never redefine an inherited non-virtual function.
如果重新定義從基類繼承的non-virtual function, 那么就應該是設計出現了問題37: 絕不重新定義繼承而來的缺省參數值(針對virtual函數)
virtual函數系動態綁定,而缺省參數值卻是靜態綁定如果需要缺省值,應該將該函數設計為non-virtual函數,再設計一個private的virtual function 如同前面介紹的Template Method設計38: Model “has-a”or “is-implementated-in-terms-of” through composition
在應用域內,是has-a的關系在實現域中,是根據某物實現出 例如STL庫中的stack是以vector為底層數據結構實現的39: 明智而謹慎地使用private繼承
private繼承某種意義上說 is-implemented-in-terms-of, 所以盡可能使用38條中使用復合的方式實現除非確定private繼承可以造成empty base最優化40: 明智而謹慎地使用多重繼承
多重繼承比單一繼承復雜;以及可能會對virtual繼承的需要virtual繼承會增加大小,速度,初始化復雜度等成本41: 了解隱式接口和編譯器多態
以不同的template參數具現化function templates會導致調用不同的函數,這就是編譯期多態(有點像重載函數) classes 和 templates都支持接口(interface)和多態(polymorphism)對classes而言接口是顯式的,以函數簽名為中心。多態則是通過virtual函數發生于運行期對template參數而言,接口是隱式(implicit)的,奠基于有效表達式;多態則是通過template具現化發生于編譯期42: 了解typename的雙重意義
聲明template參數時,class和typename可以互換記得使用typename標識嵌套從屬類型名稱;但不要在base class List或 member initialization list內以它作為base class的修飾符43: 學習處理模板化基類內的名稱
繼承模板基類的時候,基類的成員函數不可見;這個時候可以用一下三種方式解決 在前面添加this指針利用using語句,使模板基類的函數可見利用基類的類名加::調用基類函數44: 將與參數無關的代碼抽離template
template生成多個classes和多個function,所以任何template代碼都不該與某個造成膨脹(怎么造成代碼膨脹呢?)的template參數產生相依關系因非類型模板參數而造成的代碼膨脹,往往可以消除,做法是以函數參數或class成員變量替換template參數因類型參數(type parameters)而造成的代碼膨脹,往往可以降低,做法是讓帶有王權相同的二進制表述的instantiation types共享實現碼45: 運用成員函數模板接受所有的兼容類型
請使用member function templates生成“可接受所有兼容類型”的函數如果你聲明member templates 用于“泛化copy構造”或泛化assignment操作,還是需要聲明正常的拷貝構造函數和copy assignment操作符46: 需要類型轉換時請為模板定義非成員函數 編寫class template,需要支持與此template相關函數 支持參數之隱式類型轉換 時,請將那些函數定義為 class template內部的friend函數47: 請使用traits classes表現類型信息
Trait classes使得“類型相關信息”在編譯器可以用,以template特化實現整合overloading技術48: 認識template元編程
49: 了解new-handler的行為
new-handler函數要完成以下的事情:
讓更多內存可被使用安裝另一個new-handler(令new-handler修改會影響new-handler行為的static數據,global數據)卸除new-handler(將null指針傳遞給set_new_handler)跑出bad_alloc(或派生出來的)異常Nothrow new是一個頗為局限的工具,因為它只適用于內存分配 夠來的構造函數還是可能跑出異常
50: 了解new和delete的合理替換時機
51: 編寫new和delete時需要固守常規
52: 寫了placement new也要寫placement delete(不熟悉)
53: pay attention to compiler warnings
編譯器的警告能力是不一樣的; 編寫程序,爭取完全無警告54: 讓自己熟悉TR1在內的標準程序庫(現在C++11基本都實現了)
55: 讓自己熟悉boost程序庫(泛型編程, 模板元編程)
沒有理解和不太懂的地方,也有幾處,主要集中于:
泛型編程模板元編程trait type、trait class, 特化以及偏特化接下來的學習之路也需要偏重以下上述方面。
新聞熱點
疑難解答
圖片精選