# Effective C++ - [细读 Effective C++](https://harttle.land/effective-cpp.html) - [《Effective C++》讀後感——從自己的角度講一講《Effective C++》](https://jonny.vip/2020/06/02/effective-cplusplus-%E8%AE%80%E5%BE%8C%E6%84%9F/) - [【Effective C++ 閱讀筆記】](https://lkm543.medium.com/effective-c-%E9%96%B1%E8%AE%80%E7%AD%86%E8%A8%98-fb68f00a6b83) ### Chapter 1: 習慣 C++ #### 01 視 C++ 為語言聯邦 - C++ 可以被視為四種語言的集合 - C : 高效程式語言 - Objected-Oriented C++ : C with classes - Template C++ : 樣板、泛型程式設計 - STL : 標準庫 #### 02 盡量以 const, enum, inline 取代 define - 使用 const 變數取代 define 的原因 - `確保 identifier 有進入 symbol table`,容易 debug - 使用常數 `const double AspectRatio = 1.653` 相比於 `#define` 可以產生更小的 object code - 無法使用 private 、 public 等封裝 - 使用 enum 取代 define,定義 class 內靜態變數 - 方法一 ```c++= class Game { private: static const int GameTurn = 10; int scores[GameTurn]; }; const int Game::GameTurn; ``` - 方法二 ```c++= class Game { private: static const int GameTurn; }; const int Game::GameTurn = 10; ``` - 方法三 `enum hack` ```c++= class Game { private: enum {GameTurn = 10}; int scores[GameTurn]; }; ``` - 使用 inline 取代 define,避免不預期行為 ```c++= #define CALL_WITH_MAX(a, b) f( (a) > (b) ? (a) : (b) ) void f(int a) {} CALL_WITH_MAX(++a, b); // a : 5 -> 7 CALL_WITH_MAX(++a, b+10); // a : 7 -> 8 // 建議做法 template<typename T> inline void CALL_WITH_MAX(const T& a , const T& b){ f(a>b?a:b); } ``` #### 03 盡量使用 const - 使用 const 語意約束,可以獲得編譯器的協助 - const 指針用法: ```c++= int *const ptr = &num; // 不可指向不同位置 int const *ptr2 = &num; // 指向 const int const int *ptr3 = &num; // 指向 const int const vector<int>::iterator 等效於 T* const // 不可指向不同位置 vector<int>::const_iterator 等效於 const T* // 指向 const int ``` - 避免用戶錯誤使用 ```c++= const Rational operator* (const Rational& lhs, const Rational& rhs); if(a*b=c){...} // 避免了此錯誤 ``` - Mutable 的使用方法 ```c++= class CTextBlock { public: std::size_t length() const{ if (!lengthIsValid) { textLength = std::strlen(pText); lengthIsValid = true; } return textLength; }; private: char *pText; mutable std::size_t textLength; // these data members may mutable bool lengthIsValid; // always be modified }; ``` - 利用 non-const 版本呼叫 const 版本,避免代碼重複 ```c++= class Example { public: // const 版本的函式 int getValue() const { std::cout << "Calling const version" << std::endl; // Step 1 // Step 2 // Step 3 return value; } // non-const 版本的函式,利用 const_cast 呼叫 const 版本 int getValue() { std::cout << "Calling non-const version" << std::endl; return const_cast<const Example*>(this)->getValue(); } private: int value = 42; }; ``` #### 04 確保 object 被使用前有初始化 - 讀取未初始化的值,會導致不明確的行為 - 對於 class 而言,賦值和初始化不同,`建議使用成員初始列 member initialization list` ```c++= Data::Data(const std::string& name) { // 兩次操作,先 default 初始化,才 copy assignment theName = name; } Data::Data(const std::string& name) // 一次操作,copy constructor : theName = name { } ``` - `不同檔案中的 non-local static, 初始化順序無法確定` - 可以使用 `Singleton Pattern` 解決 ```c++= // a.cpp FileSystem tfs = tfs(); Directory td = td(); // b.cpp 避免 a.cpp 使用這些變數時還未初始化,包成 function FileSystem& tfs() { static FileSystem fs; return fs; } Directory& tempDir() { static Directory td; return td; } ``` ### Chapter 2: 建構子/解構子/賦值運算 #### 05 了解 C++ 自動產生哪些函數 - 當這些函數被需要調用,編譯器會自動建立出來,他們都是 non-virtual - default constructor - copy constructor - destructor - copy assignment #### 06 若不想用自動產生的函數,就明確拒絕 - 將該 member function 放到 private - 將該 member function 宣告為 delete - 將該 class 繼承自一個 Uncopyable 的 class #### 07 為多型 base class 宣告 virtual destructor - (1) 為了`多型設計`所定義的 base class 必須宣告 virtual destructor - 當 base class 指針,指向 derived class 的物件時,呼叫 delete 要能正確釋放 derived class 的記憶體 - 非為了多型而設計的類別不在此限 - (2) 帶有 virtual function 的 class 都應該要有 virtual destructor - (3) 不去繼承沒有提供 virtual destructor 的類別,如 STL 中的 string, vector, ... ,否則 base class pointer to derived class 呼叫 delete 會出現未定義行為 ```c++= class SpecialString: public std::string{} // 會有問題 SpecialString *pss = new SpecialString("data"); std::string *ps; ps = pss; delete ps; // 未定義行為 ``` #### 08 別讓 exception 逃離 destructor - 當 destructor 可能出現 exception 時 - 用 `try ... catch` 去捕捉它,紀錄錯誤 - 用 `try ... catch` 去捕捉它,終止程式 - 重新設計 class 讓客戶端可以對出現的異常做出反應 ```c++= class DBConn{ public: void close() { db.close(); closed = true; } ~DBConn() { if(!closed){ try { db.close(); } catch (...) { // log of abort } } } private: DBConnetion db; bool closed; } ``` #### 09 不應該在 constructor(建構子)或 destructor(解構子)中呼叫 virtual function - 當前的類別尚未被完整初始化或完全解構,因此其 virtual dispatch(虛擬函式調用機制)無法正常運作。 ```c++= #include <iostream> class Base { public: Base() { std::cout << "Base Constructor" << std::endl; init(); // 嘗試在 constructor 中呼叫 virtual function } virtual ~Base() { std::cout << "Base Destructor" << std::endl; cleanup(); // 嘗試在 destructor 中呼叫 virtual function } virtual void init() { std::cout << "Base init" << std::endl; } virtual void cleanup() { std::cout << "Base cleanup" << std::endl; } }; class Derived : public Base { public: Derived() { std::cout << "Derived Constructor" << std::endl; } ~Derived() { std::cout << "Derived Destructor" << std::endl; } void init() override { std::cout << "Derived init" << std::endl; } void cleanup() override { std::cout << "Derived cleanup" << std::endl; } }; int main() { Derived d; return 0; } ``` ```c++= Base Constructor Base init // 並非 Derived init Derived Constructor Derived Destructor Base Destructor Base cleanup // 並非 Derived cleanup ``` #### 10 令 operator= 返回 reference to \*this - 這是個協議,並無強制性,許多 STL 類型也都是如此時做的。 - 目的是為了處理連鎖形式 ```c++= x = y = z = 15 class Widget{ Widget& operator+=(const Widget& rhs){ //... return *this; // left value } Widget& operator=(const Widget& rhs){ //... return *this; // left value } } ``` #### 11 在 operator= 中處理自我賦值 - 解決的問題 ```c++= Widget w; w = w; // 自我賦值 a[i] = a[j] // 可能自我賦值 *px = *py // 可能自我賦值 void func(Base& rb, Derived* pd) // 可能同一對象 ``` - 方法一 :identity test ```c++= Widget::operator=(const Widget& rhs){ if(this == &rhs) return *this; delete pb; pb = new Bitmap(*rhs.pb); return *this; } ``` - 方法二 :安排檢查順序 ```c++= Widget::operator=(const Widget& rhs){ Bitmap* pOrig = pb; pb = new Bitmap(*rhs.pb); delete pOrig; return *this; } ``` - 方法三 :copy and swap ```c++= Widget::operator=(const Widget& rhs){ Widget tmp(rhs); swap(tmp); return *this; } ``` #### 12 複製 object 時記得每一個部分 - 自定義 copy constructor 和 copy assignment 時,要注意每一個部分都要處理到 - 新增變數時 : (編譯器不會警告) - 記得更新 copy constructor - 記得更新 assignment - 繼承其他 class 時 : - copy constructor 要呼叫 base class 的 copy constructor - copy assignment 要呼叫 base class 的 copy assignment - copy constructor 和 copy assignment 不應該互相呼叫 - 將重複的部份抽離到第三個 function 讓他們共同使用 ### Chapter 3: 資源管理 #### 13 以 object 管理資源 - 資源 : memory、file descriptor、mutex、dbConnection、socket - 物件的 constructor 獲得資源,物件的 desctructor 釋放資源 - RAII (資源取得即初始化) : 將資源的生命週期與物件的生命週期綁定 - (1) 若變數,可以使用智能指標 (呼叫 delete) - (2) 若陣列,可以使用 vector 或是 string #### 14 資源管理的四種 copy 行為 - 使用 object 管理資源,需注意當 object 複製或轉移給另一個 object 時,底層的資源該如何對應的轉移 - (1)禁止複製,如自己實作的 `lock` ```c++= class Lock { public: explicit Lock(Mutex* pm) : mutexPtr(pm) { lock(mutexPtr); } ~Lock() { unlock(mutexPtr); } private: Lock(const Lock&) = delete; // 禁止 copy constructor Lock& operator=(const Lock&) = delete; // 禁止 copy assignment Mutex *mutexPtr; } ``` - (2)紀錄 reference count 並共享資源,如`shared_ptr` ```c++= shared_ptr<int> ptr1 = make_shared<int>(); shared_ptr<int> ptr2 = ptr1; ``` - (3)深度複製資源,如 `string` ```c++= string a = "aaaaa"; string b = a; // deep copy ``` - (4)轉移資源,如 `unique_ptr` ```c++= unique_ptr<int> ptr1 = make_unique<int>(); unique_ptr<int> ptr2 = move(ptr1); ``` #### 15 資源管理提供對原始資料的訪問 - APIs 往往要求訪問原始資源,所以每一個 RAII class 應該提供取得資源的方法 - 使用 `unique_ptr` 和 `shared_ptr` 管理資源時,若有需要得到原始指標,可以使用 `get()` ```C++= shared_ptr<int> pInv(new int); unique_ptr<int> pInv2(new int); int* p1 = pInv.get(); int* p2 = pInv.get(); ``` - 若使用自定義 class 管理資源時,最好也提供得到原始資源的方式 ```c++= // 自定義 class class Font { public: FontHandle get() const { return f; } // 提供顯式轉換 operator FontHandle() const { return f;} // 提供隱式轉換,較方便,但非常不建議 private: FontHandle f; } ``` #### 16 成對的使用 new 和 delete - 如果呼叫 new 時有使用 [], 則呼叫 delete 時也要使用 [];如果呼叫 new 時未使用 [], 則呼叫 delete 時也不該使用 []; - 當錯誤使用時,會產生未定義行為 ```c++= string* p1 = new string; string* p2 = new string[100]; delete p1; delete [] p2; ``` - 最好不要對 array 做 typedef 動作 #### 17 以獨立語句將 newed object 放入智能指針 - 使用獨立語句,可以在 exception 發生時避免資源洩漏 ```c++= // 不建議的寫法 f1(unique_ptr<int>(new int), f2()) // 建議的寫法 unique_ptr<int> x(new int); f1(x, f2()); ``` - 不確定 f2 發生異常時,unique_ptr 是否有正確釋放資源,e.g. - (1) 執行 new int - (2) 執行 f2() --> 可能 raise exception - (3) 呼叫 unique_ptr constructor ### Chapter 4: 設計和宣告 #### 18 讓接口容易被正確使用,不易被誤用 - 不要預設 client 會記住所有細節 - 促進正確使用 : 自訂 class/type 保持和 int 有"一致"的行為 - 阻止誤用 : 限制操作類型,限制對象值 #### 19 設計 class 如同 type - 設計 class 需考慮的面向 - (1) 新的 type 如何被創建和銷毀 - (2) 物件的初始化和賦值有什麼差別 - (3) 新的 type 如果 pass by value 行為為何 - (4) 新的 type 合法值為何 - (5) 新的 type 需要配合某繼承圖嗎 - (6) 新的 type 需要什麼樣的轉換 - (7) 新的 type 需要什麼樣的操作符和函數 - (8) 新的 type 需要拒絕什麼樣的函數 - (9) 誰能存取新的 type - (10) 新的 type 有哪些潛在的 interface - (11) 新的 type 有多麽一般化 - (12) 你真的需要新 type 嗎 -> 也許定義非成員函數或者模板即可 #### 20 以 pass by reference to const 取代 pass by value - (1)避免多次調用 constructor 和 destructor 造成效率浪費 - (2)避免傳遞時,資料被切割 slicing ```c++= bool validate(Student s); // pass by value --> bool validate(const Student &s); // pass by reference to const ``` #### 21 函數回傳值不可以是 reference - 若回傳值是在 stack 上的 local 變數的 reference 會存取不合法記憶體 - 若回傳值是在 heap 上記憶體的 reference,客戶端很可能沒正確釋放記憶體 - 若回傳值是 class 的 static 對象的 reference,客戶端很可能出現錯誤邏輯 - 正確:回傳值為一個新 object,return value instead of reference #### 22 將 member variable 聲明為 private - 保持 client 端都是使用小括號存取成員變數的一致性 - 透過成員函數設定每個成員變數的存取權限 - 將成員變數封裝在 class 中,保持實作的彈性 - `封裝的重要性遠比你最初見到他還重要` - 從封裝的角度,只有兩種訪問權限 `private(有封裝)` 和 `其他(不封裝)` - 宣告為 `public` ,所有客戶端都可能使用邏輯 - 宣告為 `protected` ,所有 derived class 都可能使用邏輯 #### 23 以 non-member、non-friend 替換一般 member function - 一味地去追求物件導向程式設計的守則,`有時候`並不是一個好的選擇 - 對於底下的例子,使用(1)一般 function 比使用 (2)member function 帶來更好的封裝性 ```c++= // (1) 一般 function void clearEverything()(WebBrowser &wb){ wb.clearCache(); wb.clearHistory(); wb.removeCookies(); } // (2) member function class WebBrowser { public: void clearEverything(){ wb.clearCache(); wb.clearHistory(); wb.removeCookies(); } } ``` #### 24 若所有參數都需要類型轉換,請使用 non-member function - 如果 constructor 為 non-explicit ```c++= class Rational { public: Rational(int n = 0, int d = 1) : num(n), den(d) {} const Rational operator*(Rational rhs) const { return Rational(num * rhs.den, den* rhs.num); } int num, den; }; void f1() { Rational r1(1, 2); Rational result = r1 * 2; Rational result2 = 2 * r1; // error } ``` - 如果 constructor 為 explicit ```c++= class Rational2 { public: explicit Rational2(int n = 0, int d = 1) : num(n), den(d) {} const Rational2 operator*(Rational2 rhs) const { return Rational2(num * rhs.den, den* rhs.num); } int num, den; }; void f2() { Rational2 r2(1, 2); Rational2 result3 = r2 * 2; // error Rational2 result4 = 2 * r2; // error } ``` - 一般情況,滿足交換律,都需要類型轉換 ```c++= class Rational3 { public: Rational3(int n = 0, int d = 1) : num(n), den(d) {} int num, den; }; Rational3 operator*(Rational3 lhs, Rational3 rhs) { return Rational3(lhs.num * rhs.den, lhs.den * rhs.num); } void f3() { Rational3 r3(1, 2); Rational3 result5 = r3 * 2; // OK Rational3 result6 = 2 * r3; // OK } ``` #### 25 寫出沒有異常的 swap - 在 c++11 之後,std::swap 的底層實現是使用「移動語意」來提升性能 - 為了支持移動語意,你需要在類中實現`移動構造函數`和`移動賦值運算符` ```c++= template <typename T> void swap(T& a, T& b) { T temp = std::move(a); // 使用移動建構 a = std::move(b); // 使用移動賦值 b = std::move(temp); // 使用移動賦值 } ``` ### Chapter 5: 實作 #### 26 盡可能延後變數定義的時間 - 延後變數出現時間 - 避免 constructor 和 destructor 被呼叫到 - 迴圈的變數宣告在外部或內部 - 外部: 一個 constructor、一個 desctructor、n 個 assign - 內部: n 個 constructor、n 個 desctructor,但較好維護 #### 27 盡量少做轉型 #### 28 避免返回 handles 指向 object 內部結構 #### 29 為 "異常安全" 努力 #### 30 徹底了解 "inline" 的所有細節 #### 31 將編譯 dependency 降到最低 - 轉型 ```c++= // 舊式轉型 (T)expression T(expression) // 新式轉型 const_cast<T>(expression) dynamic_cast<T>(expression) reinterpret_cast<T>(expression) static_cast<T>(expression) // 錯誤的轉型 class SpecialWindow: public Window { public: // 假設要先調用 Window 的 onResize 才做其他操作 virtual void onResize() { static_cast<Window>(*this).onResize(); // (1) 錯誤,因為會作用在 *this 的 base class 成分副本而已 Window::onResize(); // (2) 正確 ... } } ``` - C++ 轉型 ```c++= (T) expression T (expression) const_cast<T> (expression) dynamic_cast<T> (expression) reinterpret_cast<T> (expression) static_cast<T> (expression) ``` - [const_cast-详解一篇就够了](https://blog.csdn.net/qq_45853229/article/details/124669527?spm=1001.2101.3001.6650.10&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7ERate-10-124669527-blog-81449296.pc_relevant_3mothn_strategy_recovery&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7ERate-10-124669527-blog-81449296.pc_relevant_3mothn_strategy_recovery&utm_relevant_index=14) - 呼叫 parent class 的函數 ```c++= class SpecialWindow: public Window{ public: virtual void onResize(){ Window::onResize(); // 先呼叫 parent class 的函數 // 接著做其他操作 } }; ``` - https://goodspeedlee.blogspot.com/2016/11/c.html ### Chapter 6: 繼承與物件導向 #### 32 Public 繼承滿足 "is-a" 的關係 - Liskov Substitution Principle - 適用於 base class 上的每一件事情,一定也適用於 derived class 上 - penguin 不能直接 public 繼承 bird - square 不能直接 public 繼承 rectangle #### 33 避免遮蓋繼承而來的名稱 - 依序由內至外尋找不同 scope - 變數宣告 ```c++= int x; // global variable void func(){ double x; // local variable // 若使用變數 x,會先找 local scope 再找 global scope } ``` - 一般繼承下的函數 ```c++= class Base { virtual void mf1() = 0; virtual void mf2(); void mf3(); // 以上三種行為一樣 } class Derived: public Base { virtual void mf1(); void mf4(); } int main(){ Derived d; // 會先找 derived class scope 再找 base class scope d.mf1(); // Derived::mf1 d.mf2(); // Base::mf2 d.mf3(); // Base::mf3 d.mf4(); // Derived::mf4 } ``` - `繼承 + 函數重載會造成名稱遮蓋問題` - 編譯器避免使用者定義 Derived Class 時誤用了 Base Class 中的重載函數 ```c++= class Base { virtual void mf1() = 0; virtual void mf1(int); virtual void mf2(); } class Derived: public Base { virtual void mf1(); // 名稱遮蓋 Base 中的重載函數 } int main(){ Derived d; d.mf1(); // Derived::mf1 d.mf1(3); // Error! Base::mf1(int) 被名稱遮蓋 d.mf2(); // Derived::mf2 } ``` - public 繼承下,使用 `using Base::` 解決名稱遮蓋問題,使用 Base Class 中的函數 - public 繼承應滿足 is-a 關係,正常來說,應繼承所有 Base Class 中的重載函數 ```c++= class Base { virtual void mf1() = 0; virtual void mf1(int); virtual void mf2(); } class Derived: public Base { using Base::mf1; // 看見 Base class 內所有的 mf1 virtual void mf1(); // 宣告 Derived::mf1() } int main(){ Derived d; d.mf1(); // Derived::mf1 d.mf1(3); // OK! Base::mf1 d.mf2(); // Derived::mf2 } ``` - private 繼承下,如果只想使用 Base Class 中的函數,透過 `forwarding function` ```c++= class Base { virtual void mf1(); virtual void mf1(int); virtual void mf2(); } class Derived: private Base { virtual void mf1() { // forwarding function Base::mf1(); } } int main(){ Derived d; d.mf1(); // OK! Derived::mf1() 間接呼叫 Base::mf1() d.mf1(3); // Error! Base::mf1(int) 被名稱遮蓋 } ``` #### 34 區分 interface 繼承和 implementation 繼承 - `interface 總是被繼承` - public 繼承應滿足 is-a 關係,所有對 base class 為真的事,對 derived class 也應為真 - `讓 derived class 只繼承 interface : pure virutal function` ```c++= class Shape { virtual void draw() const = 0; // pure virtual function } ``` - `讓 derived class 繼承 interface 和 default implementation` - 使用 `impure virtual function`,可能會讓 derived class 不小心繼承到不預期的 default implementation,比較不建議使用,以下提供兩種方式 - 方法一 : 使用 `pure virtual function + protected function` ```c++= class Airplane { public: virtual void fly() const = 0; // pure virtual function protected: void defaultFly() { // default implementation }; } class ModelA : public Airplane { public: void fly(){ defaultFly(); } } ``` - 方法二 : 使用 `pure virtual function + pure function implementation` ```c++= class Airplane { public: virtual void fly() const = 0; // pure virtual function } void Airplane::defaultFly() { // default implementation of pure virtual function }; class ModelA : public Airplane { public: public: void fly(){ Airplane::fly(); } } ``` - `讓 derived class 繼承 interface 和強制性的 implementation : non-virtual function` ```c++= class Shape { public: int objectID() const { if (_object_ID == 0){ _object_ID = randInt(); } return _object_ID; } } ``` #### 35 考慮 virtual function 以外的選擇 - (1) 使用 `Non Virtual Interface (NVI)` 實現 Template Method 設計模式 - 將 public virtual 拆分為 public non-virtual + private virtual - 可以做一些 preprocess/postprocess - NVI 手法並不一定要讓 virtual 函數是 private ```c++= class Game Character { public: int healthValue() const { // preprocess int retVal = doHealthValue(); // postprocess } private: virtual int doHealthValue() const { // ... } }; ``` - (2) 使用 function pointer 實現 Strategy Method 設計模式 ```c++= class EvilBadGuy: public GameCharacter { public: explicit EvilBadGuy(HealthCalcFunc hcf = defaultHealthCalc) : GameCharacter(hcf) {} }; int loseHealthQuickly(const GameCharacter&); int loseHealthSlowly(const GameCharacter&); // behavior EvilBadGuy ebg1(loseHealthQuickly); EvilBadGuy ebg2(loseHealthSlowly); ``` #### 36 絕不重新定義繼承而來的 non-virtual function - `non-virtual function 是靜態綁定的`,繼承時不應該覆寫 ```c++= class B { public: void mf(); } class D : public B { public: void mf(); } int main(){ D x; B* pB = &x; pB->mf(); // B::mf() 靜態綁定 D* pD = &x; pD->mf(); // D::mf() 靜態綁定 } ``` - `virtual function 是動態綁定的` ```c++= class B { public: virtual void mf(); } class D : public B { public: virtual void mf(); } int main(){ D x; B* pB = &x; pB->mf(); // D::mf() 動態綁定 D* pD = &x; pD->mf(); // D::mf() 動態綁定 } ``` #### 37 絕不重新定義繼承而來的 default parameter 值 - `default parameter 是靜態綁定的`,繼承時不應該覆寫 ```c++= class Shape { public: virtual void draw(ShapeColor color = Red) const = 0; }; class Circle Shape { public: virtual void draw(ShapeColor color = Green) const = 0; } int main(){ Shape *pr = new Circle; pr->draw(); // Red !!! } ``` #### 38 透過 composition 表現 "has-a" 的模型或 "is implemented in terms of" - composition 表現 "has-a" - 發生在應用域 application domain: people、car、canvas ```c++= class Person { private: string name; Address addr; PhoneNumber voiceNumber; } ``` - composition 表現 "is implemented in terms of" - 發生在實現域 implementation domain: buffer、mutex、search tree ```c++= template<class T> class Set { public: bool member(const T& item) const; void insert(const T& item); void remove(const T& item); size_t size() const; private: list<T> rep; // is implementation in terms of } ``` #### 39 private 繼承表示 "is implemented in terms of" - private 繼承 - 只是一種實現技術,所以 base class 每樣東西都成為 private,因為他們都只是實現細節 - 在設計上沒有意義,意義只在實現層面 - ```盡可能使用 composition,必要時才使用 private 繼承``` ```c++= class Widget { private: class Widget Timer: pubilc Timer { public: virtual void onTick() const; }; WidgetTimer timer; } ``` - 唯一建議使用 private 繼承的例外,`Empty Base Optimization(EBO)` ```c++= class Empty {}; // 可能有 typedefs, enums, static member 等等 class HoldsAnInt { // 應該只需要一個 int 空間,但是 member 會被 c++ 多安插一個 char 空記憶體,然後還會補上 padding 空間(放大到可以存放一個 int) private: int x; Empty e; } class HoldsAnInt: private Empty { // 根據 EBO,sizeof(int) == sizeof(HoldsAnInt) private: int x; } ``` #### 40 審慎使用多重繼承 - public 繼承應該總是 virtual public 繼承,但會造成大小、速度、初始化複雜度等成本 - 多重繼承的一個情境: public 繼承某 interface + private 繼承某 implementation ### Chapter 7: 模板和泛型 #### 41 了解 implicit interface 和 compile-time polymorphism - class : 顯式接口 (explicit interface)、運行期多型 (virtual function) - template : 隱式接口 (implicit interface)、編譯期多型 #### 42 typename 的雙重意義 - 宣告 template 參數時,typename 和 class 可互換 - 以下寫法等價 ```c++= template <class T> class Widget1; template <typename T> class Widget2 { public: Widget2() { cout << "Widget<" << typeid(T).name() << "> created" << endl; } }; int main() { Widget2<int> w1; Widget2<float> w2; return 0; } ``` - 在 template function 中的 `嵌套從屬名稱 dependent name` 需使用 typename - `從屬名稱 dependent name` : template 內出現相依於 template 參數的名稱 - `嵌套從屬名稱 nested dependent name` : 從屬名稱於 class 中呈現嵌套狀 - `非從屬名稱 non-dependent name` : 不依賴 template 參數的名稱 ```c++= template <typename C> void f2(const C& c, typename C::iterator it1) { // C 不得使用 typename,it1 必須使用 typename typename C::const_iterator it2 = c.begin(); // it2 必須使用 typename for(; it2 != c.end(); ++it2) { cout << *it2 << endl; } } int main() { f2(string("hello"), string("hello").begin()); return 0; } ``` - 在 `基類列 base class lists` 或 `成員初值列 member initial list` 內,不得使用 typename ```c++= template <typename C> class Base { public: class Nested { public: explicit Nested(int x) { cout << "Nested(" << x << ") created" << endl; } }; }; template <typename T> class Derived : public Base<T>::Nested { // 不得使用 typename public: explicit Derived(int x) : Base<T>::Nested(x) { // 不得使用 typename x++; typename Base<T>::Nested temp(x); cout << "Derived(" << x << ") created" << endl; // 必須使用 typename } }; int main() { Derived<int> d(42); return 0; } ``` - 使用 `std::iterator_traits` 的常見用法 ```c++= template <typename T> void f4(T t) { typename iterator_traits<T>::value_type temp = *t; // 必須使用 typename typedef typename iterator_traits<T>::value_type value_type; // typedef 和 typename 常常一起使用 value_type temp2 = *t; cout << typeid(temp).name() << endl; cout << typeid(temp2).name() << endl; }; int main() { std::vector<int> vec = {1, 2, 3}; f4(vec.begin()); return 0; } ``` #### 43 處理 scope to base class template - derived class template 預設不會去找 base class template 裡的 member function - 因為在模板全特化的情況下 base class template,不一定存在那個 member function ```c++= template <typename T> class MsgSender { public: void sendClear() { cout << "MsgSender::sendClear" << endl; } }; template <> // MsgSender<int> is a full specialization class MsgSender<int> {}; // which has no member function sendClear template <typename T> // this will lead to compiler error, as it won't search in base class template class LoggingMsgSender : public MsgSender<T> { public: void sendClearMsg() { cout << "LoggingMsgSender::sendClearMsg" << endl; // sendClear(); } }; ``` - 有以下三種解決方法,進入 base class template 查找 - 不管是哪一種方式,都表示向 compiler 承諾 base class template,一定存在那個 member function 的接口 ```c++= template <typename T> // method 1 class LoggingMsgSender1 : public MsgSender<T> { public: void sendClearMsg() { cout << "LoggingMsgSender1::sendClearMsg" << endl; this->sendClear(); } }; template <typename T> // method 2 class LoggingMsgSender2 : public MsgSender<T> { public: using MsgSender<T>::sendClear; void sendClearMsg() { cout << "LoggingMsgSender2::sendClearMsg" << endl; sendClear(); } }; template <typename T> // method 3 class LoggingMsgSender3 : public MsgSender<T> { public: void sendClearMsg() { cout << "LoggingMsgSender3::sendClearMsg" << endl; MsgSender<T>::sendClear(); } }; ``` #### 44 將與參數無關的程式碼抽離 template - 用心感受 template 被具現化多次時可能的重複 - `非類型模板參數 non type template parameters` 造成的程式碼膨脹,通常可以函數參數化和 class member 替換 template 參數來降低 ```c++= // causing binary codes duplication template <typename T, size_t N> class SquareMatrix1 { public: void invert() { cout << "Implementation of invert " << typeid(T).name() << " " << N << endl; } private: T data[N * N]; }; // avoid binary codes duplication, but performance may be worse template <typename T> class SquareMatrixBase { protected: SquareMatrixBase(size_t n, T* pMem) : size(n), pData(pMem) {}; void invert(size_t matrixSize) { cout << "Implementation of invert " << typeid(T).name() << endl; } private: size_t size; T* pData; }; template <typename T, size_t N> class SquareMatrix2 : private SquareMatrixBase<T> { public: SquareMatrix2() : SquareMatrixBase<T>(N, data) {}; void invert() { cout << "Wrapper of invert " << typeid(T).name() << " " << N << endl; SquareMatrixBase<T>::invert(N); } private: T data[N * N]; }; int main() { SquareMatrix1<int, 5> m1; SquareMatrix1<int, 10> m2; m1.invert(); m2.invert(); SquareMatrix2<int, 5> m3; SquareMatrix2<int, 10> m4; m3.invert(); m4.invert(); return 0; } ``` - `類型參數 type template parameters` 造成的程式碼膨脹,可透過共享完全相同的 binary representation 來降低 #### 45 使用 member function template - 在 template class 必須使用 `member function template` 達成 `implicit conversion from derived class pointer to base class pointer` - 原本的 class 繼承關係支援 `implicit conversion` ```c++= class Top {}; class Middle : public Top {}; class Bottom : public Middle {}; void f1(){ // OK, implicit conversion Top* pt1 = new Middle; Top* pt2 = new Bottom; const Top* pct1 = pt1; } ``` - 以下的 template class 不支援 `implicit conversion` ```c++= template <typename T> class SmartPtr { public: explicit SmartPtr(T* realPtr) : ptr(realPtr) {} private: T* ptr; }; void f2(){ // Compile error, don't know how to implict conversion SmartPtr<Top> sp1 = SmartPtr<Middle>(new Middle); SmartPtr<Top> sp2 = SmartPtr<Bottom>(new Bottom); SmartPtr<const Top> sp3 = sp1; } ``` - 使用 `member function template` 支援 `implicit conversion` ```c++= template <typename T> class SmartPtr2 { public: explicit SmartPtr2(T* realPtr) : ptr(realPtr) {} template <typename U> SmartPtr2(const SmartPtr2<U>& other) : ptr(other.get()) {} T* get() const { return ptr; } private: T* ptr; }; void f3(){ // support implicit conversion from derived to base SmartPtr2<Top> sp1 = SmartPtr2<Middle>(new Middle); SmartPtr2<Top> sp2 = SmartPtr2<Bottom>(new Bottom); SmartPtr2<const Top> sp3 = sp1; // from base to derived will cause compile error SmartPtr2<Middle> sp4 = SmartPtr2<Top>(new Top); } ``` #### 46 使用 non-member function template - 單純使用 template non-member function,沒有能力自動做類型轉換 ```c++= template<typename T> class Rational { public: Rational(const T& n = 0, const T& d = 1) : num(n), den(d) {} T num, den; }; template<typename T> Rational<T> operator*(Rational<T> &lhs, Rational<T> &rhs) { return Rational<T>(lhs.num * rhs.den, lhs.den * rhs.num); } void f() { Rational<int> r1(1, 2); Rational<int> result = r1 * 2; // Error Rational<int> result2 = 2 * r1; // Error } ``` - 改法一:可以 compile 但不能 link,因為編譯器找到 declaration 但找不到 implementation ```c++= template<typename T> class Rational { public: Rational(const T& n = 0, const T& d = 1) : num(n), den(d) {} T num, den; friend const Rational<T> operator*(const Rational<T> &lhs, const Rational<T> &rhs); }; template<typename T> const Rational<T> operator*(const Rational<T> &lhs, const Rational<T> &rhs) { return Rational<T>(lhs.num * rhs.den, lhs.den * rhs.num); } void f() { Rational<int> r1(1, 2); Rational<int> result = r1 * 2; Rational<int> result2 = 2 * r1; } ``` - 改法二:在 class 中使用 friend 宣告 non-member function ```c++= template<typename T> class Rational { public: Rational(const T& n = 0, const T& d = 1) : num(n), den(d) {} T num, den; friend const Rational<T> operator*(const Rational<T> &lhs, const Rational<T> &rhs) { return Rational<T>(lhs.num * rhs.den, lhs.den * rhs.num); } }; void f() { Rational<int> r1(1, 2); Rational<int> result = r1 * 2; // OK Rational<int> result2 = 2 * r1; // OK } ``` - 改法三 : 讓 friend 函數調用輔助函數 ```c++= template<typename T> class Rational; template <typename T> const Rational<T> doMultiply(const Rational<T> &lhs, const Rational<T> &rhs) { return Rational<T>(lhs.num * rhs.num, lhs.den * rhs.den); } template<typename T> class Rational { public: Rational(const T& n = 0, const T& d = 1) : num(n), den(d) {} T num, den; friend const Rational<T> operator*(const Rational<T> &lhs, const Rational<T> &rhs) { return doMultiply(lhs, rhs); } }; void f() { Rational<int> r1(1, 5); Rational<int> result = r1 * 6; // OK Rational<int> result2 = 7 * r1; // OK } ``` #### 47 使用 traits class 表現 types 資訊 - 使用 `std::iterator_traits` 得到 type 資訊 ```c++= #include <iterator> #include <typeinfo> #include <vector> #include <list> #include <iostream> using namespace std; namespace other{ struct input_iterator_tag {}; struct output_iterator_tag {}; struct forward_iterator_tag : public input_iterator_tag {}; struct bidirectional_iterator_tag : public forward_iterator_tag {}; struct random_access_iterator_tag : public bidirectional_iterator_tag {}; // check type at runtime template<typename IterT, typename destT> void advance1(IterT& it, destT d) { if(typeid(typename iterator_traits<IterT>::iterator_category) == typeid(random_access_iterator_tag)) { it += d; } else { if( d > 0 ) { while( d-- ) {++it;} } else { while( d++ ) {--it;} } } } // check type at compile time template<typename IterT, typename destT> void doAdvance(IterT& it, destT d, std::random_access_iterator_tag) { it += d; } template<typename IterT, typename destT> void doAdvance(IterT& it, destT d, std::input_iterator_tag) { if (d < 0) throw "input iterator does not support negative advance"; while(d--) { ++it; } } template<typename IterT, typename destT> void doAdvance(IterT& it, destT d, std::bidirectional_iterator_tag) { if( d > 0 ) { while( d-- ) {++it;} } else { while( d++ ) {--it;} } } template<typename IterT, typename destT> void advance2(IterT& it, destT d) { doAdvance(it, d, typename iterator_traits<IterT>::iterator_category()); } } int main() { vector<int> v = {1, 2, 3, 4, 5, 6, 7}; auto it = v.begin(); other::advance1(it, 3); // OK cout << *it << endl; other::advance2(it, 2); // OK cout << *it << endl; // list<int>::iterator it2; // other::advance1(it2, 3); // compile error return 0; } ``` #### 48 認識 template metaprogramming - 將工作由 runtime 移往 compile time,實現早期錯誤偵測和更高執行效率 - 情境一: 確保度量單位正確 - 情境二: 優化矩陣運算 - 情境三: 生成客戶訂製的 design pattern ```c++= #include <iostream> using namespace std; template <unsigned n> struct Factorial { static const unsigned value = n * Factorial<n - 1>::value; }; template <> struct Factorial<0> { static const unsigned value = 1; }; int main() { cout << Factorial<5> :: value << endl; return 0; } ``` ### Chapter 8: 客製化 new 和 delete #### 49 了解 new-handler 的行為 #### 50 了解 new 和 delete 的合理替換時機 #### 51 編寫 new 和 delete 的慣例 #### 52 寫了 placement new 也要寫 placement delete - 了解 new_handler 行為 ```c++= // global 設定 #include <iostream> #include <new> using namespace std; // example1 of using typedef of a function ============================== typedef void (*func)(int); func f; void example1(){ f = [](int x) { cout << x << endl; }; f(5); } // example2 of using std::set_new_handler ============================== void outOfMem1() { cerr << "Unable to satisfy request for memory1\n"; abort(); } void example2(){ std::set_new_handler(outOfMem1); int *p = new int[1000000000000]; } // example3 customize new handler class C3 { public: static void* operator new(size_t size) { cout << "Using operator new(size), size: " << size << endl; return ::operator new(size); } static void* operator new[](size_t size) { cout << "Using operator new[](size), size: " << size << endl; return ::operator new(size); } private: int i, j, k; }; void example3() { C3 *p = new C3; delete p; C3 *p2 = new C3[10]; delete[] p2; } // example4 of using new_handler with class ============================== class NewHandlerHolder { public: explicit NewHandlerHolder(std::new_handler nh) : handler(nh) {} ~NewHandlerHolder() { std::set_new_handler(handler); } private: std::new_handler handler; NewHandlerHolder(const NewHandlerHolder&); // prevent copying NewHandlerHolder& operator=(const NewHandlerHolder&); // prevent copying }; class Widget { public: static std::new_handler set_new_handler(std::new_handler p) noexcept(false) { std::new_handler oldHandler = currentHandler; currentHandler = p; return oldHandler; } static void* operator new[](size_t size) { std::cout << "size: " << size << std::endl; NewHandlerHolder h(std::set_new_handler(currentHandler)); return ::operator new(size); } private: static std::new_handler currentHandler; }; std::new_handler Widget::currentHandler = 0; // initial to NULL void outOfMem4() { cerr << "Unable to satisfy request for memory4\n"; abort(); } void example4(){ Widget::set_new_handler(outOfMem4); Widget *pw1 = new Widget[1000000000000]; } // example5 of using new_handler with template of class ============================== template<typename T> class NewHandlerSupport { public: static std::new_handler set_new_handler(std::new_handler p) throw() { std::new_handler oldHandler = currentHandler; currentHandler = p; return oldHandler; } static void* operator new[](size_t size) { NewHandlerHolder h(std::set_new_handler(currentHandler)); return ::operator new(size); } private: static std::new_handler currentHandler; }; template<typename T> std::new_handler NewHandlerSupport<T>::currentHandler = 0; // initial to NULL class Widget5 : public NewHandlerSupport<Widget5> { public: static void outOfMem5() { cerr << "Unable to satisfy request for memory5\n"; abort(); } }; void example5(){ Widget5::set_new_handler(Widget5::outOfMem5); Widget5 *pw5 = new Widget5[1000000000000]; } // example6 using nothrow new operator ============================== void example6(){ try { int *p = new int[1000000000000]; // throw std::bad_alloc if failed if (p == nullptr) { cerr << "Get nullptr\n"; } } catch (std::bad_alloc) { cerr << "Get std::bad_alloc\n"; } try { int *p = new (std::nothrow) int[1000000000000]; // return nullptr if failed if (p == nullptr) { cerr << "Get nullptr\n"; } } catch (std::bad_alloc) { cerr << "Get std::bad_alloc\n"; } } // main function int main() { example5(); return 0; } // g++ -std=c++11 49.cpp -o 49 && ./49 ``` - 了解 new 和 delete 的合理替換時機 - 檢測運用錯誤 - 強化效能 - 收集使用上統計資訊 - 實作自訂 new 和 delete 的規則 - operator new 會被 derived class 繼承 - 寫了 placement new 也要寫 placement delete ### Chapter 9: 雜項討論 #### 53 不要輕忽編譯器警告 #### 54 熟悉 TR1 等標準程式庫 #### 55 熟悉 Boost 等程式庫 ###### tags: `筆記`