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 %}