# 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 = # // 不可指向不同位置
int const *ptr2 = # // 指向 const int
const int *ptr3 = # // 指向 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: `筆記`