2018-01-02
課後複習筆記
Ch1 基本
01:聯邦
準則就是因地制宜
02:避免 #define
- 編譯器看不到
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:默默函式
- default constructor (若沒其他 ctor)
- destructor (non-virtual by default)
- copy constructor
- copy assignment
哪時用了?
做了些啥?
- 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;
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
12:複製物件所有成分
- 哪些成分?
- all members → 尤其增添成員時
- base classes → 非常容易忘記
- 哪些函式?
- copy constructor
- copy assignment
- all the other
operator =
s
copy constructor 不該呼叫 copy assignment,反過來也不行,
應該由第三個函式來消除重複程式碼。
Ch3 資源管理
13:以物件管理資源
不要再寫超級難以維護的程式碼了! – 多想幾秒,你可以寫物件。
- 拿到就托管 (RAII)
- 利用 destructor
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
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);
25:swap
- swap 用途廣泛:條款 11、條款 29
std::swap()
之實作
- 需可複製:copy construct, copy assignment
- 有效率問題才自訂 swap
- pImpl 設計
- 資料全部放在
FooImpl* pImpl
裡
- swap 便交換指標即可
- 背景知識
- 自訂 swap
- 全特化
std::swap
→ 不適用於 class template
- 以 member swap function 取代 friend function → 循 STL 慣例
- 實作自己的 swap (利用 Koenig loopup) → 通用
- 但針對一些
白目,還是得準備 std::swap
版
- 最好在自己的 namespace,避免在 global。
- 使用 swap
using std::swap;
swap(a, b)
(赤裸裸的)
- member swap function 不要拋出異常 → 條款 29
- C++11: move semantics → stack overflow
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。
- 誤用一:(其實不太懂)
- 誤用二:
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
- 無資料敗壞 → 如:真的發生才改變狀態
- 保證
- 異常拋出,值仍有效
- 異常拋出,值仍不變
- 不拋出異常
- 實現技巧
- 坨屎壞粥
30:inline
- pros & cons
- 無呼叫成本
- 可供最佳化
- 目的碼 縮小/膨脹 → paging, instruction cache hit rate (why?), …
- 重新編譯 vs 重新連結
- 不易 break point (?
- 寫法
- 明:
inline
- 暗:在 class 內定義 (含 friend)
- 必須定義於 header file (的原因)
- 編譯器可以拒絕 → 看 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 也可以
- 例一:鳥會飛?
- 飛鳥/非飛鳥 → 繼承體系混亂
- 企鵝可以飛,只是會拋出錯誤 → 執行期間會才知道
- 嚴格說來鳥不應具飛的函式
- 例二:正方形是矩形? → 加寬 可否適用於正方形?
33:避免遮掩繼承而來的名稱
- 與 global/local scope 的道理相同 – 型態不同也會遮蔽 (無論變數或函式)
- 重載函式 ← 問題所在
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
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
以上兩者意思相同
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
- 繼承 template class 時,若未知其確切型別,則無法得知繼承而來的成員是否存在。
- 解法 → 承諾它在
this->
using
my_base<T>::
- 承諾未兌現 → 仍將編譯失敗
- 及早報錯:解析 template → 具現化 template
44:留意程式碼膨脹
- template:寫一次,具現化多次 (用到才具現化)
- 抽出相同部份 → template 恐因具現化多次而程式碼膨脹
- 解法
- 共用函式 → 置於 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
- 內建型別:
iterator_trait<char *>::categroy
→ 偏特化方式聲明
- compile time 的 if-else:overloading
iterator_trait
的其他資訊;其他的 trait。
48:超編程!
- 在 compile time 就完成 runtime 的事
- 提早發現問題
- 更小的執行檔;更短的執行期;更少的記憶體。
- 花更久的時間編譯
- 條款 47 (typeid/trait) 便是一例
- 什麼都可以算 → boost MPL 提供高階語法
- TMP 入門:計算階乘
- 實務應用
Ch8 NEW/DELETE
49:new handler
- new 不出來…
- 曾經: return
NULL
- 現在: throw
std::bad_alloc
std::set_new_handler
- 不斷呼叫該 handler 直到成功
- 小心 infinite loop/recursive
- handler 可以…
- 騰出空間
- 去找其他 handler
- 裝死:
set_new_handler(nullptr)
- 丟
std::bad_alloc
- 自爆:abort 或 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?
- 應反覆呼叫 new handler → 條款 51
- alignment! alignment! alignment! → 否則 變慢 或甚至 硬體異常
- 其實 debug 下多已內建
51:自幹 new/delete 的 SOP
(global/member) operator new
- 成功:回傳指標 (指向該記憶體)
- 失敗:
- 要求 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
55:熟悉 boost
The Boost C++ Libraries