《C++編程規範——101條規則、準則與最佳實務》(C++ Coding Standards——101 Rules, Guidelines and Best Practices)
類的設計與繼承
第32條(C):弄清所要編寫的是哪種類
第33條(C):用小類代替巨類
分而治之。用類表示概念。
第34條(B):用組合代替繼承
即優先使用委託而非繼承。
第35條(B):避免從並非要設計成基類的類中繼承
本意是要獨立使用的類所遵守的設計藍圖和基類大不相同,將獨立類用作基類是一種嚴重的設計錯誤。
第36條(C):優先提供抽象介面
偏愛抽象藝術吧。抽象介面是完全由(純)虛函數構成的抽象類別,沒有狀態(即沒有成員資料),通常也沒有成員函數實現。注意:在抽象介面中避免使用狀態能夠簡化整個階層的設計。
依賴倒置原理(DIP):1)高層模組不應該依賴低層模組。相反,兩者都應該依賴於抽象。2)抽象不應該依賴細節(實現)。相反,細節應該依賴抽象。通常說的“要面向介面編程,而不要面向實現編程”也是這個意思。
第37條(C):公有繼承即可替換性。繼承,不是為了重用,而是為了被重用
繼承塑模的是“是一個(is a kind of )”關係【Liskov替換原則】。組合塑模的是“有一個(has a kind of )”關係。
第38條(D):實施安全的覆蓋
第39條(D):考慮將虛擬函式宣告為非公有的,將公有函式宣告為非虛擬
在基類中進行修改代價是高昂的(尤其對於架構或庫)。非虛擬介面模式(NonVirtual Interface, NVI)。
第40條(A):要避免提供隱式轉換
explicit建構函式和命名的轉換函式。
第41條(A):將資料成員設為私人的,無行為的聚集除外(即C語言形式的struct)
保護資料具有公有資料的所有缺點。可以考慮使用Pimpl慣用法用來隱藏類的私人資料成員。
第42條(C):不要公開內部資料
隱藏資料卻又暴露控制代碼是一種自欺欺人的做法,就像鎖上了自家的門,卻把鑰匙留在了鎖上。
第43條(D):明智的使用Pimpl慣用法
// 將類的私人資料隱藏在一個不透明的指標後面class Map{ public: // .... 介面private: struct PrivateImpl; // 類Map的巢狀型別 shared_ptr<PrivateImpl> m_Impl;};
第44條(D):優先編寫非成員非友元函數
要避免較成員費:儘可能優先指定為非成員非友元函數。1)減少依賴;2)分離巨類;3)提供通用性。
第45條(D):總是一起提供new和delete
第46條(D):如果提供類專門的new,應該提供所有的標準形式(普通,就地和不拋出)
構造、析構與複製
第47條(A):以同樣的順序定義和初始化成員變數
如果違反了該條規則,也會違反第1條 在高警告層級下乾淨利落地進行編譯。
第48條(A):在建構函式中用初始化代替賦值
這可不是不成熟的最佳化,這是在避免不成熟的劣化。
第49條(B):避免在建構函式和解構函式中調用虛擬函數
這一點其實很好理解:因為在構造期間,對象還是不完整的,如果在基類的建構函式中調用了虛擬函數,那麼調用的將是基類的虛擬函數(不管衍生類別是否對該虛擬函數進行了改寫)。C++標準為什麼這樣?試想:如果調用的是衍生類別改寫後的虛擬函數版本,那麼會發生什麼事情?衍生類別改寫該虛擬函數勢必會調用衍生類別的成員資料吧?而在構造基類期間,衍生類別的資料成員還沒有被初始化,使用了未初始化的資料,正是通往未定義行為的快速列車。
// 使用工廠函數插入“後建構函式”調用class B{ protect: B() {/*.... */ } virtual void PostInitialize() {/*....*/} public: template<typename T> static shared_ptr<T> create() // 函數模板 { shared_ptr<T> p(new T); p->PostInitialize(); return p; }};class D : public B{// ....};shared_ptr<D> pD = D::create<D>(); // 建立一個D的對象
第50條(A):將基類解構函式設為公用且虛擬,或者保護且非虛擬
第51條(D):解構函式、釋放和交換絕對不能失敗
第52條(D):一致地進行複製和銷毀
通常,拷貝建構函式,複製賦值操作符函數,解構函式要麼都定義,要麼都不定義。
第53條(D):顯示地啟用或者禁止複製
第54條(D):避免切片。在基類中考慮用複製代替複製
將基類的拷貝建構函式聲明為受保護的protected, 這樣就不能將衍生類別對象直接傳遞給接收基類對象的函數,從而防止了對象切片。取而代之在基類中增加一個複製函數clone()的定義,並採用NVI模式實現。在公有的非虛擬介面clone()函數中採用斷言檢查繼承自基類的所有衍生類別是否忘記了重寫virtual B *doClone()。
第55條(D):使用賦值的標準形式
第56條(D):只要可行,就提供不會失敗的swap()(而且要正確的提供)
模板與泛型
第64條(C):理智的結合靜態多態性和動態多態性
動態多態性是以某些類的繼承體系出現的,通過虛擬函數和(指向繼承層次中的對象的)指標或引用來實現的。靜態多態性則是通過類模板和函數模板實現。
第65條(D):有意的進行顯示自訂
第66條(D):不要特化函數模板
第67條(D):不要無意地編寫不通用的代碼
STL:容器
第76條(A):預設時使用vetor。否則,選擇其他合適的容器
第77條(B):從vector和string代替數組
第78條(A):使用vector和string::c_str與非C++的API交換資料
vector的儲存區總是連續的;大多數標準庫對string的實現,也使用連續記憶體區(但是不能得到保證)。string::c_str總是返回一個Null 字元'\0'結束的C風格字串。string::data也是返回指向連續記憶體的指標,但不保證以Null 字元'\0'結束。
第79條(D):在容器中只儲存值和智能指標
第80條(B):用push_pack代替其他擴充序列的方式
第81條(D):多用範圍操作,少用單元素操作
第82條(D):使用公認的慣用法真正的壓縮容量,真正的刪除元素
container<T>(c).swap(c); // 去除多餘容量的
shrink-to-fit慣用法container<T>().swap(c); // 清空容器c
c.erase(remove(c.begin(), c.end(), value), c.end()); // 刪除容器c中所有等於value的元素, erase-remove慣用法
STL:演算法
演算法即迴圈——只是更好。演算法是迴圈的模式。開始使用演算法,也就意味著開始使用函數對象和謂詞。
第83條(D):使用帶檢查的STL實現
什麼是帶檢查的STL實現?
第84條(C):用演算法調用代替手工編寫的迴圈
有意識的熟悉,使用STL演算法吧。
第85條(C):使用正確的STL尋找演算法
find/find_if, count/count_if, binary_search, lower_bound, upper_bound, equal_range
第86條(C):使用正確的STL排序演算法
partition, stable_partition, nth_element, partial_sort, partial_sort_copy, sort, stable_sort
第87條(C):使謂詞成為純函數
第88條(C):演算法和比較子的參數應多用函數對象少用函數
第89條(D):正確編寫函數對象
模板與泛型程式設計,C++標準模板庫STL一直是自己很薄弱的地方,因為在工作中很少使用。這一來是因為自己起初就對這一塊不熟悉,進而導致編程時很少使用(都不知道用有哪些功能啊),而越是這樣,使用得越少,就更沒有機會是熟悉STL,正是形成一個迴圈。STL有很多的實用功能,以後要有意識的加以使用,學習,爭取掌握它。