pyhton資料結構與演算法——棧與佇列
Effective C++
參考:
- Effective C++, 3rd edition
trivial tips
技術
-
C++ 是多正規化語言
- 視 C++ 為一個多正規化程式語言,C++ 支援過程、OO、OB、泛型、函數語言程式設計與超程式設計等正規化(1)
-
儘量避免巨集
#define
的使用(2) -
儘可能使用 const 關鍵字(3)
-
注意容器中
iterator
和const_iterator
之間的區別,這其實是語法上的無奈 -
注意 bitwise const 和 logical const 的區別,前者是編譯器對 const 的實現方法,後者是對 const 的用法
-
雖然不應該使用型別轉換但為了避免重複可以在 non-const 函式種呼叫 const 函式。不要在 const 函式中呼叫 non-const 函式,這樣違反了語義
return const_cast<char&>( // 非安全轉型,去除返回值中的 const 修飾 static_cast<const T&>(*this).ret_char_ref(); // 安全轉型,給當前物件加上 const 修飾 );
-
-
不要混淆賦值與初始化(4)
- 注意跨編譯單元的初始化無序問題;常用的解決辦法是使用 local static 替換 no-local static
-
編譯器自動生成 Big5 的原則
- 如果沒有定義且可行則編譯器自動為物件生成拷貝構造、拷貝賦值和解構函式(預設非 virtual);如果沒有自定義建構函式則編譯器會生成一個預設建構函式 (5)
-
class A: private Uncopyable {...};
(6) -
給多型基類的解構函式加上 virtual,注意這裡的兩個關鍵詞:多型&基類 (7)
- 多型場景下,沒有 virtual 析構的類不應該被繼承,不然會因區域性析構的問題造成記憶體洩漏等異常。C++ 中 的 string 和一些容器,比如 vector/map/set 等都沒有 virtual 析構,所以要避免繼承這些類,儘量使用 final
- 不要給不需要的類加上 virtual,避免不必要的效能損失
- 多型場景下,沒有 virtual 析構的類不應該被繼承,不然會因區域性析構的問題造成記憶體洩漏等異常。C++ 中 的 string 和一些容器,比如 vector/map/set 等都沒有 virtual 析構,所以要避免繼承這些類,儘量使用 final
-
不要讓解構函式拋異常,一個合適的解決方法是做兩手準備,將在析構中可能拋異常的動作獨立出去,作為一個可執行函式,然後在析構中呼叫並嘗試捕獲異常,如此使用者就可以自行選擇呼叫方式。析構中捕獲異常要麼直接 abort,要麼吞掉異常(8)
-
不要在構造與解構函式中呼叫 virtual 函式(9)
-
inline (30)
- 類宣告(標頭檔案)中定義的函式預設 inline
- 有被取地址操作的 inline 函式會被剝奪 inline 屬性;很多除錯場景,inline 預設是禁止的;構造與解構函式的 inline 是非常差的設計
設計
-
令 operator = 返回 reference to *this,a = b = c (10)
-
使用證同測試在 operator = 中處理自我賦值;更好的辦法是使用異常安全程式碼:使用 swap。沒有處理自我賦值的拷貝賦值可能造成資源意外釋放等問題(11)
A& operator=(const A& rhs) { A tmp(rhs); swap(*this, tmp); return *this; }
-
使用獨立的語句將資源置入自能指標中,避免呼叫異常時造成資源洩漏(17)
//A的建立/fun的執行/智慧指標的建立,隨編譯器實現而不同,一旦出現異常很可能出現資源洩漏 process(std::shared_ptr<A>(new A()), fun()); // 錯誤用法 // 下面的寫法比較合適 auto sptr = std::shared_ptr<A>(new A()); process(sptr, fun());
-
適當的引入新型別可以減少不必要的錯誤,比如引入 Day/Month/Year 類,可以減少引數填些的錯誤(18)
-
封裝意味著減少資訊的洩漏,非 private 的成員變數與更多的成員函式都會洩漏更多的資訊(22、23)
- 將所有成員變數設定為 private 雖然在某些場景下增加了程式碼量但提高了程式碼的維護提供了彈性(22)
- 可能的話,使用 non-member/non-friend 函式代替 member 函式(23)
- 面向物件提倡封裝,non-member/non-friend 可以提供比 member 更高的封裝效能。封裝程度越高意味著物件向外提供的資訊越少,從實現上看就是對成員變數的訪問(成員函式)更少
- 非必要的 helper 函式可以放到其他編譯單元中以減少程式碼之間的依賴
-
使用 non-member 函式實現所有引數的自動型別轉換,例如 class A 與常量之間的乘法(24)
-
儘量少做轉型(27)
- 常見轉型工具
const_cast
,唯一能去除變數const屬性的 C++-style 轉型工具dynamic_cast
,效能差,常見的實現方法是字串比較,繼承鏈越長比較次數越多interpret_cast
static_cast
,一般會生成一個原物件的副本 ,強制隱式轉換
- 預設轉型會造成編譯器額外的工作,比如繼承關係中同一個物件可能有不同指標指向不同位置表示不同物件
- 常見轉型工具
-
保證異常安全:不洩漏資源 & 不破壞資料,常常可以使用 copy-and-swap 方式實現異常安全(29)
- 不洩漏資源:RAII,例如
lock_guard
/智慧指標;特別注意new/delete之間的程式碼 - 不破壞資料:比如異常安全的 swap 會先構造一個臨時變數而不是直接賦值,避免賦值失敗對原始資料的破壞。這裡的原則是:all or nothing,修改原始資料前準備好一切,直接 swap 就可以修改原始變數
- 不洩漏資源:RAII,例如
-
減少編譯依賴,最好實現標頭檔案中“只有宣告式”(31)
- C++ 編譯依賴的一個原因:編譯時編譯器需要知道物件的大小以分配記憶體。Java 等語言不存在此類問題,因為 Java 中所有物件都在堆中,棧中分配一個指標大小的記憶體空間即可
- 手段(依賴宣告式而非定義式)
- 儘量使用前置宣告,會因為 string 是 typedef 所以 string 前置宣告比較複雜,不過標準庫依賴對編譯效能的影響不大
- 優先使用指標與引用:類內能使用飲用或者指標,就不要使用物件
- Handle class:如果可以,儘量使用宣告式頭取代定義式頭。比如 Person & PersonImpl,前者為宣告頭(fwd),後者為定義,前者在內部使用後者的指標即可實現很多的依賴去除;或者直接使用介面與繼承
面向物件
- 面向行為繼承。public 繼承意味著 is-a,這裡的 is 是面向行為(函式)的。企鵝是鳥單不能飛,所以企鵝不應該繼承自鳥(32)
- 區分介面繼承與實現即成,impure virtual fun(34)
- 絕不重寫繼承而來的非虛擬函式(36)
- 不修改繼承而來的虛擬函式預設引數(37)
- 虛擬函式的替代與優化方法(35)
- NVI(non-virtual interface),類似裝飾模式。使用非虛擬函式作為介面,在非虛擬函式中呼叫虛擬函式。NVI 的好處是可以在虛擬函式前後執行一些準備與清理任務而又有虛擬函式的靈活性
- 使用 functor/bind 實現策略模式,比繼承實現的策略模式更靈活
模板(略)
new&delete
- 可以給 new 提供一個函式,申請記憶體失敗時呼叫