2018-01-02 # [Effective C++](https://www.tenlong.com.tw/products/9789861543550) 課後複習筆記 # Ch1 基本 ## 01:聯邦 - C - 物件導向 - Template - STL 準則就是因地制宜 ## 02:避免 `#define` ```cpp #define ASPECT_RATIO 1.89 const double AspectRatio = 1.89; ``` - 編譯器看不到 `ASPECT_RATIO` * 錯誤訊息看不懂 * 目的碼較大 * 無作用域、無法封裝 - static const member * 可能需要定義實體 * 可能無法 in-class 設定初值 * enum hack - MACRO 超雷!! → inline function ## 03:const - pointer/iterator 皆有兩種 const - 令 operator 回傳 const 物件 - const 物件只能呼叫 const method * const 也能多載 → 如 `operator []` * physical/logical constness 與 `mutable` - const 與 non-const 間的重複程式碼 → const 者較嚴謹作為 callee ## 04:初始化 - array (C lang) 無;vector (C++/STL) 有。 - primitive type → 請手動;object → 用 constructor。 - initialize 與 assign 不同,請使用 initialization list。 * member primitive type → 請手動 * member object → 隱含呼叫 default constructor * 怕忘記就全列上 * const member, reference 還不得不用! - DRY 的權衡 - 初始化的次序 - 跨編譯單元初始化次序 (solution: singleton) # Ch2 默默函式 ## 05:默默函式 1. default constructor (若沒其他 ctor) 1. destructor (non-virtual by default) 1. copy constructor 1. copy assignment 哪時用了? ```cpp Foo f; // ctor/dtor Foo f(g); // copy ctor f = g; // assign ``` 做了些啥? - construct/destruct * base classes' * (non-static) members' - copy * member primitive type → 複製 * member object → 呼叫對應函式 (恐遭拒) * const member, reference 沒法 copy assign → 自行處理 ## 06:不要默默函式 - private 且 不予實作 - (以 private 方式) 繼承 uncopyable 延伸學習:(C++11) [= delete;](http://en.cppreference.com/w/cpp/language/copy_constructor) ## 07:polymorphism + virtual destructor - 需要 virtual destructor 的原因 → 衍生成份未能銷毀 * 繼承 class 時 (尤 STL) 請當心!! 註:++雖可繼承非 virtual dtor 的 uncopyable,但不該用在 polymorphism。++ - 實現 virtual function 的代價 * 物件大小增加 → vptr * 不再是 POD (plain old data) - pure virtual destructor 還是需要找個地方實作 延伸學習:virtual table ## 08:別從 destructor 丟出 exception 離開 scope 就會呼叫 destructor 若拋出異常時又再碰到異常,程式會爆炸 → destructor 必須吞下異常 ## 09:ctor/dtor 不可呼叫 virtual function 在 base class 的 ctor/dtor 裡呼叫 virtual function → 是 base class 的實作 畢竟 derived class 尚未初始化/已完成解構。 - 特別小心間接的呼叫!! - 在 initialization list 使用 private static 輔助函式的技巧 ## 10:`operator =` 要回傳 reference to `*this` - 以便 `x = y = z = 3` - `+=`、`-=`、`*=` 也要 ## 11:`operator =` 的 self assignment - 你可能沒想到「自我賦值」會這樣發生 - 解決技巧 * 證同測試 * 精心巧妙的順序 * copy & swap ## 12:複製物件所有成分 - 哪些成分? * all members → 尤其增添成員時 * base classes → 非常容易忘記 - 哪些函式? * copy constructor * copy assignment * all the other `operator =`s copy constructor 不該呼叫 copy assignment,反過來也不行, 應該由第三個函式來消除重複程式碼。 # Ch3 資源管理 ## 13:以物件管理資源 *不要再寫超級難以維護的程式碼了!* -- 多想幾秒,你可以寫物件。 - 拿到就托管 (RAII) - 利用 destructor ```cpp shared_ptr<Jason> pJason(new Jason); ``` smart pointer - `auto_ptr` 禁止複製,舊值成為 NULL。 - `shared_ptr` 可以複製 * 故可用於 STL * 小心 circular reference - `delete []`? * 改用 STL * `boost::scoped_array`, `boost::shared_array` ## 14:當心複製 總是思考:複製時,該發生什麼事? - uncopyable - reference count * `shared_ptr` 可利用 deleter 自訂行為 * 例子中巧妙利用 destructor 行為 - deep copy - transfer * `auto_ptr` ## 15:要能存取原始資源 總有不完美的時候… - 顯式 * `pJason.get()` * (微隱式) `*pJason`、`pJason->method()` - 隱式 * `operator TYPE() const {}` * 固然方便,但易產生資源管理漏洞。 → 封裝 or not 封裝,自行斟酌。 ## 16:成對使用 `new`/`delete` - `new` 和 `delete` 所做的事 - 混用的後果 - 別 `typedef` 陣列 ## 17:`shared_ptr` 和 `new` 之間,容不下其他人! - explicit constructor 的效用 - 各引數的執行順序並無規定 → 要是 `new` 到建構 `shared_ptr` 之間發生意外!? # Ch4 設計 - 菜鳥:令軟體做出你希望他做的事情 - 大師:讓介面容易使用,不容易誤用 目標:正確性、高效性、封裝性、維護性、延展性、一致性 ## 18:避免誤用 - 客人誤用,你也有責任! - Date class 的例子 * 避免順序錯誤 * 避免範圍錯誤 - `operator *` 回傳 `const` type - STL 皆有 `.size()` → 婊 Java、嗆 C♯ - 索性回傳 `shared_ptr` * 避免忘了 delete * 避免錯用 delete 方式 * cross-DLL problem (? * `shared_ptr` 的成本/效益 ## 19:class = type 謹慎、細心斟酌 - construct/destruct - construct/assignment - pass by value - valid value - inherit - type cast * non-explicit-one-argument constructor * operator T - interface (public methods) - 默默函式 - public/protected/private, friend - time-complexity, exception-safe, thread-safe - generalize/template - Do you really need a class? ## 20:pass by constant reference - call-by-value 的成本 - 勿忘 const - 物件切割 - 斟酌 - 小 class? * 程式碼優化 * 未來可能變大 ## 21:return a reference? always ask: reference to what? - reference to a local (stack) object? - reference to a allocated (heap) object? - reference to a static object? 延伸閱讀:C++11 move semantic ## 22:private member - getter/setter 寫法 - 封裝的精神:為實作提供彈性 - protected 亦同 ## 23:non-member non-friend function 取代 member function - 少一個 function 能存取 member - namespace 的擴充能力 ## 24:non-member operator - 數值型別 (如:大數、有理數) 適合隱式轉換 - `Foo operator *(const Foo &rhs) const;` * `x * 2` → `x.operator *(2)` ok (隱式型別轉換:被當參數時才會) * `2 * x` → `2.operator *(x)` error!! - `Foo operator *(const Foo &lhs, const Foo &rhs);` * 且不必為 `friend` ## 25:swap - swap 用途廣泛:條款 11、條款 29 - `std::swap()` 之實作 * 需可複製:copy construct, copy assignment * 有效率問題才自訂 swap - pImpl 設計 * 資料全部放在 `FooImpl* pImpl` 裡 * swap 便交換指標即可 - 背景知識 * 全特化/偏特化 + function template 不可偏特化 → 重載即可 * 擴充 std 命名空間之禁忌 → [Extending the namespace std](http://en.cppreference.com/w/cpp/language/extending_std) - 自訂 swap * 全特化 `std::swap` → 不適用於 class template * 以 member swap function 取代 friend function → 循 STL 慣例 * 實作自己的 swap (利用 Koenig loopup) → 通用 + 但針對一些 ~~白目~~,還是得準備 `std::swap` 版 + 最好在自己的 namespace,避免在 global。 - 使用 swap 1. `using std::swap;` 2. `swap(a, b)` (赤裸裸的) - member swap function 不要拋出異常 → 條款 29 - C++11: move semantics → [stack overflow](https://stackoverflow.com/questions/8617305) # Ch5 實作 ## 26:拖延症不是病 - 建構/解構皆成本 → 不要宣告卻不使用 * 怎麼可能!? - 精髓:賦值 constructor 優於 default constructor + assignment - 迴圈內/迴圈外 ## 27:儘量不要轉型 珍惜 C++ 強型別,遠離轉型。 - 舊式 * `(T)expr` (C style) * `T(expr)` (function style) - 新式 * `const_cast<T>(expr)` * `dynamic_cast<T>(expr)` * `reinterpret_cast<T>(expr)` * `static_cast<T>(expr)` - 新式較好 * 十分醒目:容易發現問題所在 * 功能窄化:減少不預期的行為 - 在 explicit constructor 時使用 `const_cast`? - 轉型很忙 * `int` ↔ `double` * `base *pb = &d;` 多重繼承之故,指標可能 offset。 - 誤用一:(其實不太懂) * 正確用法 `base::method();` - 誤用二:`dynamic_cast` 成本高 * 直接操作 derived 類別 * 從 base 使用 virtual function (取捨) - 誤用三:連串 `dynamic_cast` ## 28:avoid handle to member - `Content& getContent() const;` (×) 可藉 `Content&` 修改內容 - `const Content& getContent() const;` (△) 有 dangling 風險 * 不過 STL 的 `operator[]` 也都這樣 * 使用時盡量降低 dangling 風險 - **handle** → reference、pointer、iterator - **member** → 變數、函式 ## 29:異常安全保證 - 安全 * 不洩漏資源 → 如:改用 auto lock * 無資料敗壞 → 如:真的發生才改變狀態 - 保證 * 異常拋出,值仍有效 * 異常拋出,值仍不變 * 不拋出異常 - 實現技巧 * copy & swap - 坨屎壞粥 ## 30:inline - pros & cons * 無呼叫成本 * 可供最佳化 * 目的碼 縮小/膨脹 → paging, instruction cache hit rate (why?), ... * 重新編譯 vs 重新連結 * 不易 break point (? - 寫法 * 明:`inline` * 暗:在 class 內定義 (含 friend) - 必須定義於 header file (的原因) * 別與 template 搞混! - 編譯器可以拒絕 → 看 warning * 太複雜的函式 (迴圈、遞迴) * virtual 函式 (runtime 才知道) - inline function pointer → 仍產生函式本體 - inline constructor/destructor → 因為其實做了很多事 (即便看來是空的),可能不適合。 ## 31:compile dependency - 目標:唯有介面改動才慘烈重編 → 給外人知道越少越好 - 介面/實作 * C++: 要配置多少記憶體空間?? * Java: 全部指標 - 方法一:pImpl * 前置宣告 - 方法二:abstract base class * 記得 virtual destructor * factory - 成本 # Ch6 物件 ## 32:public 繼承 = is-a 最重要的條款 - 所有 B 可派上用場之處 D 也可以 - 例一:鳥會飛? 1. 飛鳥/非飛鳥 → 繼承體系混亂 1. 企鵝可以飛,只是會拋出錯誤 → 執行期間會才知道 1. 嚴格說來鳥不應具飛的函式 - 例二:正方形是矩形? → *加寬* 可否適用於正方形? ## 33:避免遮掩繼承而來的名稱 - 與 global/local scope 的道理相同 -- 型態不同也會遮蔽 (無論變數或函式) - 重載函式 ← 問題所在 * using * inline 轉交 ## 34:繼承介面 & 繼承實作 - **pure virtual** -- 只繼承介面 其實仍可提供實作 - **impure virtual** -- 繼承介面、預設實作 可能忘記自訂實作 * 可改用 pure virtual + (protected) default implement → 強迫明確聲明 (inline?) * 或乾脆 pure virtual 並提供實作 - **non-virtual** -- 繼承介面、不准改變實作 務必審慎評估使用 ## 35:virtual 以外的方法 請多多比較各種方法 - non-virtual interface (NVI) * 該流派:virtual 應該總是 private * 可進行 事先/事後 處理 * 重新定義繼承來的 private virtaul 是 OK 的 - function pointer * 相同 class 可用不同 function;不同 class 可用相同 function。 * 可動態替換 * 只能從 public 介面存取主物件 - function object * 更彈性於 function pointer * 搭配 bind 更強大 - 傳統 strategy * 可擴充 base ## 36:別覆寫繼承來的普通函式 (non-virtual) - 由於靜態綁定,即便是同一物件,行為會受 pointer/reference 型別不同而有所不同。 - 理論上若真要如此就該 virtual ## 37:別改變繼承來的預設參數 - 繼承 non-virtual function (x);繼承 virtual function (o) → 條款 36 - 動態綁定 & 靜態型別 * virtual function 動態綁定;預設參數卻根據靜態型別。 * **Why?** 比較簡單比較快 - 維護相同預設參數很麻煩 → NVI ## 38:複合 = has-a 或 據該物實作 重點不是區分 has-a 與 據該物實作,而是區分兩者與 is-a。 而 據該物實作 尤其容易與 is-a 混淆 例:NuThread、SocketHelper ## 39:private 繼承 - 行為 * 不自動轉型為 base * 繼承來的都是 private - 意義:據該物實作 (繼承實作,不要介面) * 是的,與 複合 相同 * 盡量使用 複合 - 必要才用 private 繼承 * 存取 protected 成員 * 重新定義 virtual 函式 → 總能改用 複合 * empty base optimization ## 40:多重繼承 - 兩個 base 的成員有一樣的名稱? * 先最佳匹配,才檢查可用性。 (最配卻不可用) * ambiguous! * 明確指出以解決歧義:`mp.BaseA::foobar()` - 鑽石型多重繼承 (virtual base class) * 共同的 base 可以共用一份 → `class foo : virtual public bar {};` * virtual 繼承的成本 + 總需透過指標參照 ① 耗用一個指標的空間 ② dereference 指標的時間 + 初始化順序較為複雜 → 由最底層負責 * 非必要別用,或別在 virtual base 中放置資料。 - 最後提供一個例子:`class CPerson : public IPerson, private PersonInfo {};` # Ch7 TEMPLATE ## 41:隱式介面/編譯期多型 - 物件導向:顯式介面、執行期多型 → 明確由函式簽名式取決 - 泛型編程:隱式介面、編譯期多型 → 憑具現化能否成功取決 ## 42:typename ```cpp template <class T> ... template <typename T> ... ``` 以上兩者意思相同 **dependent name 問題** `T::iterator * x` - 是 型別 還是 變數? - 碰到 dependent name 就以 `typename` 標明 - 例外:`typename` 不可出現在… * base class list * initialization list - 若 dependent name 太長可以先 `typedef` 起來 如:`typedef typename std::iterator_trait<T>::value_type value_type;` ## 43:來自 template base class ```cpp template <typename T> class my_base {}; template <typename T> class my_class : public my_base<T> {}; ``` - 繼承 template class 時,若未知其確切型別,則無法得知繼承而來的成員是否存在。 * 因為可能特化 (CompanyZ 為例) - 解法 → 承諾它在 * `this->` * `using` * `my_base<T>::` - 承諾未兌現 → 仍將編譯失敗 - 及早報錯:解析 template → 具現化 template ## 44:留意程式碼膨脹 - template:寫一次,具現化多次 (用到才具現化) - 抽出相同部份 → template 恐因具現化多次而程式碼膨脹 ```cpp template <typename T, size_t N> class matrix {}; matrix<int, 3> p; matrix<int, 4> q; matrix<int, 5> r; ``` - 解法 * 共用函式 → 置於 base class * 避免反覆傳遞參數 → 記為 member (斟酌 protected) - 優劣分析 - 膨脹現象在 `tyepname T`、`size_t N` 兩種參數皆存在 ## 45:member function template - smart pointer:以物件仿造指標,以提供額外功能。 * `auto_ptr`、`shared_ptr` -- 自動釋放資源 * iterator -- 移至下個節點 - 別忘仿造 隱式轉換 -- ① base/derived ② const/non-const * `B* p = new D;` * `smart_ptr<B> p = smart_ptr<D>(new D);` → 問題在 `smart_ptr<B>` 與 `smart_ptr<D>` 完全無關 - **How?** * member function (此為 copy ctor) template (拜託不要列舉 * 不需 `explicit` * 限制轉換對象:交由原始指標處理 - `shared_ptr` 為例 * 不限於 ctor * 額外支援 `weak_ptr`、`auto_ptr` (但 `explicit`) * `auto_ptr` 的 reference 並不為 `const` - 仍要防止默默函式默默產生 ## 46:non-member operator (template ver.) - 延伸 條款 24,在 template 版本,`x * 2` 便不能成立。 - 因為 template 引數推導的過程不考慮隱式轉換 (該函式尚未存在…) - 解法:寫在 class template 內 (成為 friend function) 可避開 function template 特有的引數推導 * **friend?** 其實不打算存取 private 成員啦~ * class template 速記式:`foo<T>` → `foo` - **link error?** 直接實作在 class template - **inline?** 不然再轉呼叫 (雖仍必須實作於標頭檔,但不再是 inline 了) ## 47:trait **5 種 iterator** | category | R/W | move | inherit | | ------------- | --- | --------- | ------- | | input | R×1 | `++` | - | | output | W×1 | `++` | - | | forward | ∞ | `++` | input | | bidirectional | ∞ | `++`/`--` | forward | | random access | ∞ | ∞ | bidirectional - 怎麼讓 `std::advance` 盡量使用 random access → 如何對型別挾帶資訊? - nesting information (`my_iterator::categroy`) 不適用於內建型別 (`char *` 也應該要可以 - trait:讓額外的型別挾帶資訊 * 自訂型別:`iterator_trait<my_iterator>::category` → 仍轉交 nesting information ```cpp template <typename T> struct iterator_trait { typedef typename T::category category; } ``` * 內建型別:`iterator_trait<char *>::categroy` → 偏特化方式聲明 ```cpp template <typename T> struct iterator_trait<T*> { typedef foobar_iterator_tag category; } ``` - compile time 的 if-else:overloading - `iterator_trait` 的其他資訊;其他的 trait。 ## 48:超編程! - 在 compile time 就完成 runtime 的事 * 提早發現問題 * 更小的執行檔;更短的執行期;更少的記憶體。 * 花更久的時間編譯 - 條款 47 (typeid/trait) 便是一例 - 什麼都可以算 → [boost MPL](http://www.boost.org/doc/libs/release/libs/mpl/) 提供高階語法 - TMP 入門:計算階乘 * 以遞迴 (具現化) 實現迴圈 * 以特化為終止條件 - 實務應用 # Ch8 NEW/DELETE ## 49:new handler - new 不出來… * **曾經:** return `NULL` * **現在:** throw `std::bad_alloc` - [`std::set_new_handler`](http://en.cppreference.com/w/cpp/memory/new/set_new_handler) * 不斷呼叫該 handler 直到成功 * 小心 infinite loop/recursive - handler 可以… * 騰出空間 * 去找其他 handler * 裝死:`set_new_handler(nullptr)` * 丟 `std::bad_alloc` * 自爆:[abort](http://en.cppreference.com/w/cpp/utility/program/abort) 或 [exit](http://en.cppreference.com/w/cpp/utility/program/exit) - nothrow new * 你 nothrow 不代表別人要 nothrow 啊!! * 所以別再用了 **令不同 class 呼叫不同 handler** - 沒有內建,所以自幹 * 提供 `.set_new_handler()` * 在 `operator new` 偷偷 `set_new_handler` * 記得恢復 → 以物件管理資源 - 不妨寫成公用 base class * 為何 template? 每個 class 要有自己的 static variable * CRTP (curiously recurring template pattern) = do it for me ## 50:啥時自幹 new/delete **Why?** - 檢查誤用 * 忘 delete (memory leak) 或 delete 多次 (不確定行為) * overrun 或 underrun → 藉 signature - 要快 * 要通用就沒有針對優化 * 可針對 固定尺寸、單執行緒 等 - 要省:省掉 cookie - 統計 * 大小、壽命、次序、變化 → 知道了才好優化吧! - alignment:也許編譯器不好 - 避免 page fault - 特殊行為:share memory、資料抹去 **How?** ```cpp void* operator new(std::size_t size) throw(std::bad_alloc) {...} ``` - 應反覆呼叫 new handler → 條款 51 - alignment! alignment! alignment! → 否則 變慢 或甚至 硬體異常 - 其實 debug 下多已內建 ## 51:自幹 new/delete 的 SOP **(global/member) operator new** - 成功:回傳指標 (指向該記憶體) - 失敗: * 有 new handler 就 call (現在有 [`get_new_handler`](http://en.cppreference.com/w/cpp/memory/new/get_new_handler) 可用了 * 沒就 throw `std::bad_alloc` - 要求 0 byte:也須給合法指標 → 給它 1 byte - 考慮被繼承的狀況:若 `size != sizeof(base)` 便改用標準 `operator new` (要是衍生類別的大小恰巧一樣!? * 獨立物件的大小不得為零 **(global/member) operater delete** - 確保 delete null 指標安全 - 對應的 `size != sizeof(base)` **(global/member) operator new[]** - 元素的個數與大小皆無法斷定 → 所以好像沒啥好做的 - 可能包含額外空間存放元素個數 題外話:base 要有 virtual dtor ## 52:placement new - placement new 是指… * 狹義:`void* operator new(std::size_t, void*)` * 廣義:任何具額外參數的 `operator new` - `p = new (true) foobar;` * `void* operator new(std::size_t, bool)` * constructor - 要是 constructor 失敗,C++ runtime 會… * 呼叫對應的 `void operator delete(void*, bool)` * 但要是沒有就算了 → 造成 memory leak - 要是使用者 `delete p;` (而非 `delete (true) p;` * 所以也要提供正常版 `operator delete` (不算使用者誤用嗎?? - placement new/delete 會遮掩正常版 new/delete (同理於條款 33) * 可提供內含全部正常版 new/delete 的 base class 以便於 using # Ch9 雜項 ## 53:重視警告 - 警告背後的成因也許與字面上不同 - 也勿過於依賴警告 (它也許不警告 - 爭取 0 警告 ## 54:熟悉標準函式庫 [cppreference.com](https://en.cppreference.com/) ## 55:熟悉 boost [The Boost C++ Libraries](https://theboostcpplibraries.com/) {%hackmd @yipo/style %}