轉:C++ Coding Standards

/*************************************************************************
Scripts of C++ Coding Standards.
There are 101 tips in all.
The start.
*************************************************************************/
組織和策略問題
0.  不要拘泥於小節(又名:瞭解哪些東西不應該標準化)
——只規定需要規定的事情:不要強制施加個人喜好或者過時的做法。
·不要規定縮進多少,應該規定要用縮進來體現代碼的結構。
·不要強制行的具體長度,應該保證代碼行的長度有利於閱讀。
·不要在命名方面規定太多,應該規定的是使用一致的命名規範。
·不要規定註釋體例(除非需要使用工具從特定的體例中提取出文檔。
——例子
·括號的位置。
·空格與製表符。
·匈牙利記法。
·單入口,單出口(single entry,single exit,SESE)。
1.  在高警告級別乾淨利落地進行編譯
——高度重視警告:使用編譯器的最高警告級別。應該要求構建是乾淨利落的(沒有警告)。理解所有的警告。通過修改代碼而不是降低警告級別來排除警告。
——排除警告的正確做法:
·把它弄清楚;然後
·改寫代碼以排除警告,並且
·使代碼閱讀者和編譯器都能更加清楚,代碼是按編寫者的意圖執行的。
——例子
·第三方頭文件
·“未使用的函數參數”(Unused function parameter)
·“定義了從未使用過的變量”(Variable defined but never used)
·“變量使用前可能未經初始化”(Variable may be used without being initialized)
·“遺漏了return語句”(Missing return)
·“有符號數/無符號數不匹配”(Signed/unsigned mismatch)
2.  使用自動化構建系統
———次按鍵就解決問題:使用完全自動化(“單操作”)的構建系統,無需用戶干預即可構建整個項目。
3.  使用版本控制系統
——好記性不如爛筆頭(中國諺語):請使用版本控制系統(version control system,VCS)。永遠不要讓文件長時間地登出。在新的單元測試通過之後,應該頻繁登入。確保登入的代碼不會影響構建功能。
4.  在代碼審查上投入
——審查代碼:更多的關注有助於提高質量。亮出自己的代碼,閱讀別人的代碼。互相學習,彼此都會受益。
設計風格
5.  一個實體應該只有一個緊湊的職責
——一次只解決一個問題:只給一個實體(變量、類、函數、名稱空間、模塊和庫)賦予一個定義良好的職責。隨着實體變大,其職責範圍自然也會擴大,但是職責不應該發散。
——例子
·realloc(在標準C語言中,realloc是一個臭名昭著的不良設計——擔當太多的任務。)
·basic_string(在標準C++語言中,basic_string是一個臭名昭著的不良設計——巨大的類設計——太臃腫。)
6.  正確、簡單和清晰第一
——軟件簡單爲美(Keep It Simple Software,KISS):正確優於速度。簡單優於複雜。清晰優於機巧。安全優於隱患。
——格言警句:
·簡單設計和清晰代碼的價值怎麼強調都不過分。——不知道
·程序必須爲閱讀它的人而縮寫,只是順便用於機器執行。——一行寫不下
·編寫程序應該以人爲本,計算機爲二。——理解就好
·計算機系統中最便宜、最快速、最可靠的組件都還不存在。——等你開發
·所缺的恰是最精確(永不出錯),最安全(堅不可摧)的簡單設計。——改動了一些
·使設計、文檔編寫、測試和維護起來儘量變容易,簡單化——也是一門學問
——例子:
·不要使用不必要的或者小聰明式的操作符重載。
·應該使用命名變量,而不要使用臨時變量,作爲構造函數的參數。
7.  編程中應知道何時和如何考慮可伸縮性
—— 小心數據的爆炸性增長:不要進行不成熟的優化,但是要密切關注漸進複雜性。處理用戶數據的算法對所處理的數據量耗費的時間應該是可預測的,最好不差於線性 關係。如果能夠證明優化必要而且非常重要,尤其在數據量逐漸增長的情況下,那麼應該集中精力改善算法的O(N)複雜性,而不是進行小型的優化,比如節省一 個多餘的加法運算。
——請預先做好下面事情:
·使用靈活的、動態分配的數據,不要使用固定大小的數組。
·瞭解算法的實際複雜性。
·優先使用線性算法或者儘可能快的算法。
·儘可能避免於線性複雜性的算法。
·永遠不要使用指數複雜性的算法,除非你已經山窮水盡,確實別無選擇。
8.  不要進行不成熟的優化
——拉丁諺語云,快馬無需鞭策:不成熟優化的誘惑非常大,而它的無效性也同樣嚴重。優化的第一原則就是,不要優化。優化的第二原則(僅使用於專家)是:還是不要優化。再三測試,而後優化。
——請記住:
·請在優化前確保你將所做的優化是確實有價值的。
·讓一個正確的程序更快速,比讓一個快速的程序正確,要容易的太多,太多。
·爲人編寫代碼。
·某一天需要優化代碼,首先要考慮算法優化,並嘗試將優化封裝和模塊化,然後註釋修改歷史。
——例子:
·inline悖論
9.  不要進行不成熟的劣化
——放鬆自己,輕鬆編程:在所有其他事情特別是代碼複雜性和可讀性都相同的情況下,一些高效的設計模式和編程慣用法會從你的指尖自然流出,而且不會比悲觀的替代方案更難寫。這並不是不成熟的優化,而是避免不必要的劣化(pessimization)。
——例子:
·在可以用通過引用傳遞的時候,卻定義了通過值傳遞的參數。
·在使用前綴++操作符很合適的場合,卻使用了後綴版本。
·在構造函數中使用賦值操作而不是初始化列表。
10.              儘量減少全局和共享數據
——共享會導致衝突:避免共享數據,尤其是全局數據。共享數據會增加耦合度,從而降低可維護性,通常還會降低性能。
——例子:
·避免使用名字空間作用域中具有外部連接的數據或者作爲靜態類成員的數據。
·全局名稱空間中的對象名稱還會污染全局名字空間。
·名字空間作用域中的對象、靜態成員對象或者跨線程或跨進程共享的對象會減少多線程和多處理器環境中的並行性,往往是產生性能和可伸縮性瓶頸的源頭。
·應儘量降低類之間的偶合,儘量減少交互。
11.              隱藏信息
——不要泄密:不要公開提供抽象的實體的內部信息。
——例子:
·應該公開抽象,而不是數據。
·不要從任何提供抽象的實體中公開數據。
·絕對不要將類的數據成員設爲public。
·絕對不要公開指向它們的指針或句柄。
12.              懂得何時和如何進行併發性編程
——線程安全地:如果應用程序使用了多個線程或者進程,應該知道如何儘量減少共享對象,以及如何安全地共享必須共享的對象。
——最重要的問題:避免死鎖,活鎖(livelock)和惡性的競爭條件(包括加鎖不足導致的崩潰)。
——若應用程序需要跨線程共享數據,請:
·參考目標平臺的文檔,瞭解該平臺的同步化原語。
·最好將平臺的原語用自己設計的抽象包裝起來。
·確保正在使用的類型在多線程程序中使用是安全的。
·參閱更多書籍。
13.              確保資源爲對象所擁有。使用顯式的RAII和智能指針。
—— 利器在手,不要在徒手爲之:C++的“資源獲取即初始化”(rscourse acquisition is initialization,RAII)慣用法是正確處理資源的利器。RAII使編譯器能夠提供強大且自動的保證,這在其他語言中是需要脆弱的手工編寫 的慣用法才能實現的。分配原始資源的時候,應該立刻將其傳遞給屬主對象。永遠不要在一條語句中分配一個以上的資源。
——應該在自己的代碼語句中執行顯式的資源分配(比如new)而且每次都應該馬上將分配的資源賦予管理對象(比如shared_ptr)。
編程風格
14.              寧要編譯時和連接時錯誤,也不要運行時錯誤
—— 能夠在編譯時做的事情,就不要推遲到運行時:編寫代碼時,應該在編譯期間使用編譯器檢查不變式(invariant),而不應該在運行時在進行檢查。運行 時檢查取決於控制流和數據的具體情況,這意味者難知道檢查是否徹底。相比而言,編譯時檢查與控制流和數據無關,一般情況下能夠獲得更高的可信度。
——充分利用C++靜態檢查功能:
·靜態檢查與數據和控制流無關。
·靜態表示的模型更加可靠。
·靜態檢查不會帶來運行時開銷。
——可用編譯時檢查代替運行時檢查的一些情況:
·編譯時布爾條件。
·編譯時多態。
·枚舉。
·向下強制。
15.              積極使用const
—— const是我們的朋友:不變的值更易於理解、跟蹤和分析,所以應該儘可能地使用常量代替變量,定義值的時候,應該把const作爲默認的選項:常量很安 全,在編譯時會對其進行檢查,而且它與C++的類型系統一渾然一體。不要強制轉換const的類型,除非要調用常量不正確的函數。
——常量正確性是值得實現的。
——不要強制轉換const,除非要調用常量不正確的函數,或者在一些很罕見的情況下,爲了解決老編譯器中不支持mutable的問題。
——例子:
·在函數聲明中,要避免將通過值傳遞的函數參數聲明爲const。
16.              避免使用宏
——實_不_相_瞞:宏是C和C++語言的抽象設施中最生硬的工具,它是披着函數外衣的飢餓的狼,很難馴服,它會我行我素地遊走於各處。要避免使用宏。
——知道嗎?
·在C++中幾乎從不需要定義宏
·可用const或enum定義易於理解的常量。
·用inline避免函數調用的開銷。
·用template指定函數系列和類型系列。
·用namespace避免名稱衝突。
·幾乎每個宏都說明程序設計語言、程序或程序員存在缺陷。
·宏表面上看起來很好,實際上卻是另一回事。
·宏會忽略作用域,忽略類型系統,忽略所有其他的語言特定和規則。
·宏還會劫持它爲文件其餘部分所定義(#define)的符號。
·宏中的錯誤可能只有在宏展開之後才能被報告出來,而不是在定義時。
——例子:
·將模板實例化傳給宏。
——使用宏的情況:
·#include保護符(guard)
·條件編譯中的#的ifdef和#if defined
·assert的實現
17.              避免使用“魔數”
——程序設計並非魔術,所以不要故弄玄虛:要避免在代碼中使用諸如42和3.14159這樣的文字常量。它們本身沒有提供任何說明,並且因爲增加了難於檢測的重複而使維護更加複雜。可以用符號名稱和表達式替換它們,比如width*aspectRatio。
——例子:
·重要的特定領域的常量應該放在名字空間一級。
·特定於類的常量。
18.              儘可能局部地聲明變量
——避免作用域膨脹,對於需求如此,對於變量也是如此。變量將引入狀態,而我們應該儘可能少地處理狀態,變量的生存期也是越短越好。
19.              總是初始化變量
——一切從白紙開始:未初始化的變量是C和C++程序中錯誤的常見來源。養成在使用內存之前先清除的習慣,可以避免這種錯誤,在定義變量的時候就將其初始化。
——例子:
·使用默認初始值或?:減少數據流和控制流的混合。
·用函數替代複雜的計算流。有時候計算值的最好方式是將計算封裝在一個函數中。
·初始化數組。
20.              避免函數過長,避免嵌套過深
——短勝於長,平優於深:過長的函數和嵌套過深的代碼塊的出現,經常是因爲沒能賦予一個函數以一個緊湊的職責所致,這兩種情況通常都能通過更好的重構予以解決。
——建議:
·儘量緊湊:對一個函數只賦予一種職責。
·不要自我重複:優先 使用命名函數,而不要讓相似的代碼片斷反覆出現。
·優先使用&&:在可以使用&&條件判斷的地方要避免使用連續嵌套的if。
·不要過分使用try:優先使用析構函數進行自動清除而避免使用try代碼塊。
·優先使用標準算法:算法比循環嵌套要少,通常也更好。
·不要根據類型標籤(type tag)進行分支(switch):優先使用多態函數。
21.              避免跨編譯單元的初始化依賴
——保持(初始化)順序:不同編譯單元中的名字空間級對象決不應該在初始化上相互依賴,因爲其初始化順序是爲定義的。這樣做會惹出很多麻煩,輕則在項目中稍做修改就會引發奇怪的崩潰,重則出現嚴重的不可移植問題----即使是同一編譯器的新版本也不行。
22.              儘量減少定義性依賴。避免循環依賴
——不要過分依賴:如果用前向聲明(forward declaration)能夠實現,那麼就不要包含(#include)定義。
——不要相互依賴:循環依賴是指兩個模塊直接或者間接地相互依賴。所謂模塊就是一個緊湊的發佈單元。相互依賴的多個模塊並不是真正的獨立模塊,而是緊緊膠着在一起的一個更大的模塊,一個更大的發佈單元。因此,循環依賴有礙於模塊性,是大型項目的禍根。請避免循環依賴。
23.              頭文件應自給自足
——各司其責:應該確保所編寫的每個頭文件都能獨立進行編譯,爲此需要包含其內容所依賴的所有頭文件。
——建議:
·不要包含並不需要的頭文件,它們只會帶來零亂的依賴性。
·構建時,獨立編譯每個頭文件,並確認沒有產生錯誤或者警告。
——例子:
·非獨立名稱。
·只在使用時才實例化成員函數模板和模板的成員函數。
24.              總是編寫內部#include保護符,決不要編寫外部#include保護符函數與操作符
——爲頭(文件)添加保護:在所有頭文件中使用帶有唯一名稱的包含保護符號(#include guard)防止無意的多次包含。
——定義包含保護符時應遵守以下規則:
·保護符使用統一名稱。
·不要自作聰明地在受保護部分的前後放置代碼或註釋。
函數與操作符
25.              正確地選擇通過值、(智能)指針或者引用傳遞參數
——正確選擇參數:分清輸入參數、輸出參數和輸入/輸出參數,分清值參數和引用參數。正確地傳遞參數。
——正確選擇參數是通過值、通過引用還是通過指針傳遞,是一種能夠最大程度提高安全性和效率的好習慣。
——選擇如何傳遞參數時,應遵循以下準則。對於只輸入(input-only)參數:
·始終用const限制所有指向只輸入參數的指針和引用。
·優先通過值來取得原始類型(如char、float)和複製開銷比較低的值對象(如Point、complex<float>)的輸入。
·如果函數需要其參數的副本,則可以考慮通過傳遞代替通過引用傳遞。
——對於輸入參數或者輸入/輸出參數:
·如果參數是可選的或者函數需要保存這個指針的副本或者操控參數的所有權,那麼應該優先通過(智能)指針傳遞。
·如果參數是必需的,而且函數無需保存指針指向參數的指針,或者無需操控其所有權,那麼應該優先通過引用傳遞。
26.              保持重載操作符的自然語義
——程序員討厭以外情況:只在有充分理由時才重載操作符,而且應該保持其自然語義;如果做到這一點很困難,那麼你可能已經誤用了操作符重載。
27.              優先使用算術操作符和賦值操作符的標準形式
——如果要定義a+b,也應該定義a+=b:在定義二元操作符時,也應該提供操作符的賦值形式,並且應該儘量減少重複,提高效率。
28.              優先使用++和—的標準形式。優先調用前綴形式
——如果定義++c,也要定義c++:遞增和遞減操作符很麻煩,因爲它們都有前綴和後綴形式,而兩種形式語義有略有不同。定義operator++和operator—時,應該模仿她們對應的內置操作符,如果不需要原值,應該優先調用前綴版本。
——對於++和—而言,後綴形式返回的是原值,而前綴形式返回的是新值。
——應該用前綴形式實現後綴形式。
29.              考慮重載以避免隱含類型轉換
——如無必要勿增對象:隱式類型轉換提供了語法上的便利,但是如果創建對象時對象的工作並必要而適於優化,那麼可以提供簽名與常見參數類型精確匹配的重載函數,而且不會導致轉換。
30.              避免重載&&,||或,(逗號)
——明智就是知道何時應該適可而止:內置的&&、||和,(逗號)得到了編譯器的特殊照顧,如果重載它們,它們就會變成普通函數,具有完全不同的語義,這肯定會引入微妙的錯誤和缺陷。不要輕率地重載這些操作符。
——例子:
·用帶有重載operator,的初始化庫,用於對序列進行初始化。
——例外:
·表達式模板庫是一個例外,設計它的目的就是用來捕獲所有操作符。
31.              不要編寫依賴於函數參數求值順序的代碼
——保持(求值)順序:函數參數的求值順序是不確定的,因此不要依賴具體的順序。
——解決方案:用命名對象控制求值順序。
類的設計與繼承
32.              弄清所要編寫的是哪種類
——瞭解自我:有很多種不同的類。弄清楚要編寫的是那一種。
——一個值類應該:
·有一個公用析構函數,複製構造函數和帶有值語義的賦值。
·沒有虛擬函數(包括析構函數)。
·是用作具體類,而不是基類。
·總是在棧中實例化,或者作爲另一個類直接包含的成員實例化。
——基類是類層次結構的構造要素。一個基類應該:
·有一個公用而且虛擬,或者保護而且非虛擬的析構函數,和一個非公用複製構造函數和賦值操作符。
·通過虛擬函數建立接口。
總是動態地在堆中實例化爲具體派生對象,並通過一個(智能)指針來使用。
——不嚴格地說,traits類是攜帶有關類型信息的模板。一個traits類應該:
·只包含typedef和靜態函數。沒有可修改的狀態或者虛擬函數。
·通常不實例化(其構造一般是被禁止的)。
——策略類(通常是模板)是可插拔行爲的片段。一個策略類應該:
·可能有也可以沒有狀態或者虛擬函數。
·通常不獨立實例化,只作爲基類或者成員。
——一個異常類應該:
·有一個公用析構函數和不會失敗(no-fail)的構造函數(特別是一個不會失敗的複製構造山書,從異常的複製構造函數拋出將使程序中止)。
·有虛擬函數,經常實現克隆和訪問。
·從std::exception虛擬派生更好。
33.              用小類代替巨類
——分而治之:小類更容易編寫,更容易保證正確,測試和使用。小類更有可能適用於各種不同情況。應該用這種小類體現簡單概念,不要用大雜燴式的類,它們要實現的概念既多又複雜。
34.              用組合代替繼承
——避免繼承帶來的重負:繼承是C++中第二個緊密的耦合關係,僅次於友元關係。緊密的偶合是一種不良現象,應該儘量避免。因此,應該用組合代替繼承,除非知道後者確實對設計者有好處。
——這裏的“組合”就是指在一個類型中嵌入另一個類型的成員變量。
——與繼承相比,組合有以下重要優點:
·在不影響調用代碼的情況下具有更大的靈活性。
·更好的編譯時隔離,更短的編譯時間。
·減少奇異現象。
·更廣的實用性。
·更健壯、更安全。
·複雜性和脆弱性降低。
——使用公用繼承模仿可替換性。
——要用非公用的繼承的情況(從常用到罕用排序):
·如果需要改寫虛擬函數。
·如果需要訪問保護成員。
·如果需要在基類之前構造已使用的對象,或者在基類之後銷燬此對象。
·如果需要操心虛擬基類。
·如果能夠確定空基類優化能帶來好處,包括這種情況下優化的確很重要,以及這種情況下目標編譯器確實能實施這種優化。
·如果需要控制多態。相當於說,如果需要可替換性關係,但是關係應該只對某些代碼可見(通過友元)。
35.              避免從並非要設計成基類的類中繼承
——有些人並不想生孩子:本意是要獨立使用的類所遵守的設計藍圖與基類不同。將獨立類用作基類是一種嚴重的設計錯誤,應該避免。要添加行爲,應該添加非成員函數而不是成員函數。要添加狀態,應該使用組合而不是繼承。要避免從具體的基類中繼承。
——例子:
·用組合代替公用繼承或者私有繼承。
·std::unary_function
36.              優先提供抽象接口
——偏愛抽象藝術吧:抽象接口有助於我們集中精力保證抽象的正確性,不至於受到實現或者狀態管理細節的干擾。優先採用實現了(建模抽象概念的)抽象接口的設計層次結構。
——應該定義和繼承抽象接口。
——抽象接口是完全由(純)虛擬函數構成的抽象類,沒有狀態(成員函數),通常也沒有成員函數實現。
——應遵循依賴倒置原理(Dependency Inversion Principle,DIP)
·高層模塊不應該依賴於低層模塊。相反,兩者都應該依賴抽象。
·抽象不應該依賴細節。相反,細節應該以來抽象。
——DIP三個基本的設計優點:
·更強的健壯性。
·更大的靈活性。
·更好的模塊性。
——例子:
·備份程序
37.              公用繼承即可替換性。繼承,不是爲了重用,而是爲了被重用
——知其然:公用繼承能夠使基類的指針或者引用實際指向某個派生類的對象,既不會破壞代碼的正確性,也不需要改變已有代碼。
38.              實施安全的改寫
——負責任地進行改寫:改寫一個虛擬函數時,應該保持可替換性:說得更具體一些,就是要保持基類中函數的前後條件。不要改變虛擬函數的默認參數。應該顯式地將改寫函數重新聲明爲virtual。謹防不小心在虛擬類中隱藏重載函數。
39.              考慮將虛擬函數聲明爲非公用的,將公用函數生命爲非虛擬的
——在基類中進行修改代價昂貴(尤其是庫中和框架中的基類):請將公用函數設爲非虛擬的。應該將虛擬函數設爲私有的,或者如果派生類需要調用基類版本,則設爲保護的。(請注意,此建議不適用與析構函數)。
——非虛擬接口(Nonvirtual Interface,NVI)模式:將公用函數設爲非虛擬的,將虛擬函數設爲私有的(或者設爲保護的,如果派生類需要調用基類的話)。
——通過將公用函數與虛擬函數分離,好處是:
·每個接口都能自然成形。
·基類擁有控制權。
·基類能夠健壯地適用變化。
——NVI對析構函數不適用,因爲它們的執行順序很特殊。
——NVI不直接支持調用者的協變返回類型。
40.              要避免提供隱式轉換
——並非所有的變化都是進步:隱式轉換所帶來的影響經常是弊大於利。在爲自定義類型提供隱式轉換之前,請三思而行,應該依賴的是顯式轉換(explicit構造函數和命名轉換函數)。
——例子:
·重載。
·錯誤都變得可行了。
41.              將數據成員設爲私有的,無行爲的聚集(C語言形式的struct)除外
—— 它們不關調用者的事:將數據成員設爲私有的。簡單的C語言形式的struct類型只是將一組值聚集在一起,並不封裝或者提供行爲,只有在這種struct 類型中纔可以將所有數據成員都設成公有的。要避免將公有數據和非公有數據混合在一起,因爲這幾乎總是是設計混亂的標誌。
——例子:
·正確封裝。
·TreeNode。
·獲取函數和設置函數。
42.              不要公開內部數據
——不要過於自動自發:避免返回類所管理的內部數據的句柄,這樣類的客戶就不會不受控制地修改對象自己擁有的狀態。
——數據隱藏是一種強大的抽象方式,也是強大的模塊化機制。
——隱藏數據卻又暴露句柄的做法只會弄巧成拙,就像你鎖上了自己家的門,卻把鑰匙留在鎖裏或吊在門口。
 
43.              明智地使用Pimpl
——抑制語言的分離慾望:C++將私有成員指定爲不可訪問的,但並沒有指定爲不可見的。雖然這樣自有其好處,但是可以考慮通過Pimpl慣用法使私有成員真正不可見,從而實現編譯器防火牆,並提高信息隱藏度。
44.              優先編寫非成員非友元函數
——要避免交成員費:儘可能將函數指定爲非成員非友元函數。
——非成員非友元函數可提高封裝性,減少耦合,提高通用性。
45.              總是一起提供new和delete
—— 它們是一攬子交易:每個類專門的重載void* operator new(parms)都必須與對應的重載void operator delete(void*,parms)相隨相伴,其中parms是額外參數類型的一個列表(第一個是std::size_t)。數組形式的new[]和 delete[]也同樣如此。
46.              如果提供類專門的new,應該提供所有標準形式(普通,就地和不拋出)析構與複製
——不要隱藏好的new:如果類定義了perator new的重載,則應該提供operator new所有三種形式——普通(plain),就地(in-place)和不拋出(nothrow)的重載。不然,類的用戶就無法看到和使用它們。
——應該總是避免隱藏就地new,因爲它在STL容器中有廣泛的使用。
——避免在客戶代碼中調用new(nothrow)版本,但仍要爲客戶提供,以免客戶一旦要用到時感到奇怪。
47.              以同樣的順序定義和初始化成員變量
——與編譯器一致:成員變量初始化的順序要與類定義中聲明的順序始終保持一致:不用考慮構造函數初始化列表中編寫的順序。要確保構造函數代碼不會導致混淆地指定不同的順序。
48.              在構造函數中用初始化代替賦值
——設置一次,到處使用:在構造函數中,使用初始化代替賦值來設置成員變量,能夠防止發生不必要的運行操作,而輸入代碼的工作量則保持不變。
——有話直說最好:在初始化列表中初始化成員變量,代碼表達意圖更加明確,而且錦上添花的是,代碼通常還更小,更快。
49.              避免在構造函數和析構函數中調用虛擬函數
—— 虛擬函數僅僅“幾乎”總是表現的虛擬:在構造函數和析構函數中,它們並不虛擬。更糟糕的是,從構造函數或析構函數直接或者間接調用未實現的純虛擬函數,會 導致未定義的行爲。如果設計方案希望從基類構造函數或析構函數虛擬分派到派生類,那麼需要採用其他技術,比如後構造函數(post- constructor)。
——從構造函數調用還完全沒有定義的純虛擬函數將是給傷口撒鹽,這中情況下的行爲是未定義的,代碼令人糊塗,維護時顯得更加脆弱。
50.              將基類析構函數設爲公用且虛擬的,或者保護且非虛擬的
——刪除,還是不刪除,這是個問題:如果允許通過基類Base的指針執行刪除操作,則Base的析構函數必須是公用且虛擬的。否則,就應該是保護且非虛擬的。
——結論:總是爲基類編寫析構函數,因爲構造函數和析構函數是公用而且虛擬的。
51.              析構函數、釋放和交換絕對不能失敗
——它們的一切嘗試都必須成功:決不允許析構函數,資源釋放(deallocation)函數(如operator delete)或者交換函數報告錯誤。說得更具體一些,就是絕對不允許將哪些析構函數可能會拋出異常的類型用於C++標準庫。
——析構函數應該設計的總能捕獲異常,而且不會讓異常傳播到析構函數之外。
——當使用異常作爲錯誤處理機制時,建議用一個註釋掉的空異常規範/*throw()*/來聲明這些函數,通過這種方式說明這一方式。
52.              一致地進行復制和銷燬
——既要創建,也要清除:如果定義了複製構造函數、複製賦值操作符或者析構函數中的任何一個,那麼可能也需要定義另一個或者另外兩個。
53.              顯式地啓用或禁止複製
——清醒地進行復制:在下述三種行爲之間謹慎選擇——使用編譯器生成的複製構造函數和賦值操作符;編寫自己的版本;如果不允許複製的話,顯式地禁用前兩者。
54.              避免切片。在基類中考慮用克隆代替複製
——切片面包很好:切片對象則不然:對象切片是自動的、不可見的,而且可能會使得漂亮的多態設計嘎然而止。在基類中,如果客戶需要進行多態(完整的、深度的)複製的話,那麼請考慮禁止複製構造函數和複製賦值操作符,而改爲提供虛擬的Clone成員函數。
55.              使用域值的標準形式
——賦值,你的任務:在實現operator=時,應該使用標準形式——具有特定簽名的非虛擬形式。
——請:
·要避免將賦值操作符設爲虛擬的。
·要始終保證複製賦值錯誤安全的,最好是提供強有力的保證。
·要確保賦值操作符是錯誤安全的,最好是提供強有力的保證。
·要顯式調用所有基類賦值操作符,併爲所有數據成員賦值。
56.              只要可行,就提供不會失敗的swap(而且要正確地提供)
——swap既可無關痛癢,又能舉足輕重:應該考慮提供一個swap函數,高效且絕對無誤地交換兩個對象。這樣的函數便於實現許多慣用法,從流暢地將對象四處移動以輕易地實現賦值,到提供一個有保證的、能夠提供強大防錯調用代碼的提交函數。
名字空間與模塊
57.              將類型及其非成員函數接口置於同一名字空間中
——非成員也是函數:如果要將非成員函數(特別是操作符和輔助函數)設計成類X的接口的一部分,那麼就必須在與X相同的名字空間中定義它們,以便正確調用。
——名字空間是管理名字和減少名字衝突的重要工具。模塊也是如此,它還是管理髮布和版本化的重要工具。
58.              應該將類型和函數分別置於不同的名字空間中,除非有意讓它們一起工作
——協助防止名字查找問題:通過將類型(以及與其直接相關的非成員函數)置於自己單獨的名字空間中,可以使類型與無意的ADL(參數依賴查找,也稱Koenig查找)隔離開來,促進有意的ADL。要避免將類型和模塊化函數或者操作符放在相同的名字空間中。
59.              不要在頭文件中或者#include之前編寫名字空間using
——名字空間using是爲了使我們更方便,而不是讓我們用來叨擾別人的:絕對不要編寫suing聲明或者在#include之前編寫using指令。
——相反,應該顯式地用名字空間限定所有的名字。
60.              要避免在不同的模塊中分配和釋放內存
—— 物歸原位:在一個模塊中分配內存,而在另一個模塊中釋放它,會在這兩個模塊之間產生微妙的遠距離依賴,使程序變得脆弱。必須用相同版本的編譯器、同樣的標 志(比較著名的如用debug還是NDEBUG)和相同的標準庫實現對它們進行編譯,實踐中,在釋放內存中,用來分配內存的模塊最好仍在內存中。
——安逸往往會導致健忘。
61.              不要在頭文件中定義具有鏈接的實體
——重複會導致膨脹:具有鏈接的實體(entity with linkage),包括名字空間級的變量或函數,都需要分配內存。在頭文件中定義這樣的實體將導致連接時錯誤或者內存的浪費。請將所有具有鏈接的實體放入實現文件。
——不要在頭文件中定義名字空間級的static實體。
——以下具有外部鏈接的實體可以放入頭文件中:
·內聯函數
·函數模板
·類模板的靜態數據成員
62.              不要允許異常跨越模塊邊界傳播
——不要想鄰家的花園拋擲石頭:C++異常處理沒有普遍通用的二進制標準。不要在兩段代碼之間傳播異常,除非能控制用來構建兩段代碼的編譯器和編譯選項;否則模塊可能無法支持可兼容地實現異常傳播。這通常可以一言以蔽之:不要允許異常跨越模塊或子系統邊界傳播。
——最低限度,應用程序必須在以下位置有捕獲所有異常的catch(…)兜底語句,其中大多數都直接使用於模塊。
·在main函數的附近。
·在從無法控制的代碼中執行回調附近。
·在線程邊界的附近。
·在模塊接口邊界的附近。
·在析構函數內部。
63.              在模塊的接口中使用具有良好可移植性的類型
——生在(模塊的)邊緣,必須格外小心:不要讓類型出現在模塊的外部接口中,除非能夠確保所有的客戶代碼能夠正確地理解該類型。應該使用客戶代碼能夠理解的最好層抽象。
模板與泛型
64.              理智地結合靜態多態性和動態多態性
——1+1可遠遠不止是2:靜態多態性和動態多態性是相輔相成的。理解它們的優缺點,善用它們的長處,結合兩者獲得兩方面的優勢。
——在C++中動態多態性最擅長於以下幾個方面:
·基於超集/子集關係的統一操作。
·靜態類型檢查。
·動態綁定和分別編譯。
·二進制接口。
  ——而在C++中靜態多態性最擅長於以下幾個方面
   ·基於語法和語義接口的統一操作
   ·靜態類型檢查
   ·靜態綁定(防止分別編譯)
   ·效率
65.              有意地進行顯式自定義
——有意勝過無意,顯式強似隱式:在編寫模板時,應該有意地、正確地提供自定義點,並清晰地記入文檔。在使用模板時,應該瞭解模板想要你如何進行自定義以將其用於你的類型,並且正確地自定義。
——爲了避免無意地提供自定義點,應該:
·將模板內部使用的任何輔助函數都放入其自己的內嵌名字空間,並用顯式的限定調用它們以禁用ADL。
·要避免依靠依賴名。
66.              不要特化函數模板
——只有在能夠正確實施的時候,特化才能起到好作用:在擴展其他人的函數模板(包括std::swap)時,要避免嘗試編寫特化代碼;相反,要編寫函數模板的重載,將其放在重載所用的類型的名字空間中。編寫自己的函數模板時,要避免鼓勵其他人直接特化函數模板本身。
67.              不要無意地編寫不通用的代碼
——依賴抽象而非細節:使用最通用、最抽象的方法來實現一個功能。
錯誤處理與異常
68.              廣泛地使用斷言記錄內部假設和不變式
——使用斷言吧!廣泛地使用assert或者等價物記錄模塊內部(也就是說,調用代碼和被調用代碼由同一個人或小組維護)的各種假設,這些假設是必須成立的,否則就說明存在編程錯誤(例如,函數的調用代碼檢查到函數的後條件不成立)。當然,要確保斷言不會產生任何副作用。
69.              建立合理的錯誤處理策略,並嚴格遵守
——應該在設計早期開發實際、一致、合理的錯誤處理策略,並予以嚴格遵守。許許多多的項目對這一點的考慮(或者錯誤估計(都相當草率,應該對此有意思地規定,並認真應用。策略必須包含以下內容:
·鑑別:哪些情況屬於錯誤。
·嚴重程度:每個錯誤的嚴重性或緊急性。
·檢查:哪些代碼負責檢查錯誤。
·傳遞:用什麼機制在模塊中報告和傳遞錯誤通知。
·處理:哪些代碼負責處理錯誤。
·報告:怎樣將錯誤記入日誌,或通知用戶。
只在模塊邊界處改變錯誤處理機制。
70.              區別錯誤與非錯誤
——違反約定就是錯誤:函數是一個工作單元。因此,失敗應該視爲錯誤,或根據其對函數的影響而定。在函數f中,當且僅當失敗違反了f的一個前條件,或者阻礙了f滿足其調用代碼的任何前條件,實現f自己的任何後條件或者重新建立f有責任維持的不變式時,失敗纔是一個錯誤。
——錯誤就是阻止函數成功操作的任何失敗。有三種類型:
·違反或者無法滿足前條件。
·無法滿足後條件。
·無法重新建立不變式。
——例子:
·std::string::insert(前條件錯誤)
·std::string::append(後條件錯誤)
·無法生成返回值(後條件錯誤)
·std::string::find_first_of(在string的上下文中不是錯誤)
·同一函數中的不同錯誤情況。
·同一情況的不同狀況。
71.              設計和編寫錯誤安全代碼
—— 承諾,但是不是懲罰:在所有函數中,都應該提供最強的安全保證,而且不應該懲罰不需要這種保證的調用代碼。至少要提供基本保證。確保出現錯誤時程序會處於 有效狀態。這是所謂的基本保證(basic guarantee)。要小心會破壞不變式的錯誤(包括但是不限於泄露),它們肯定都是bug。應該進一步保證最終狀態要麼是最初狀態(如果是錯誤,則回 滾操作),要麼是所希望的目標狀態(如果沒有錯誤,則提交操作)。這就是所謂的強保證(strong guarantee)。應該進一步保證操作永遠不會失敗。雖然這對於大多數函數來說是不可能的,但是對於析構函數釋放函數這樣的函數來說則是必須的。這就 是所謂的不會失敗保證(no-fail guarantee)。
——當有可能出現錯誤時,最安全的方法就是確保函數支持事務性的行爲。
72.              優先使用異常報告錯誤
——出現問題時,就使用異常:應該使用異常而不是錯誤來報告錯誤。但不能使用異常時,對於錯誤以及不是錯誤的情況,可以使用狀態碼(比如返回碼,errno)來報告異常。當不可能從錯誤中恢復或者不需要恢復時,可以使用其他方法,比如正常終止或者非正常終止。
——關於異常:
·異常不能不加修改地忽略。
·異常是自動傳播的。
·有了異常處理,就不必在控制流的主線中加入錯誤處理和恢復了。
·對於從構造函數和操作符報告錯誤來說,異常處理要優於其他方案。
——異常處理潛在的主要缺點在於:它要求程序員必須熟悉一些會反覆遇到的慣用法,這些管用法來源於異常的特殊控制流。
73.              通過值拋出,通過引用捕獲
——學會正確捕獲(catch):通過值(而非指針)拋出異常,通過引用(通常是const的引用)捕獲異常。這是與異常語義配合最佳的組合。當重新拋出相同的異常時,應該優先使用throw;,避免使用throw e;。
——在拋出異常時,要通過值拋出對象。
——要避免拋出指針,因爲如果拋出指針,就需要處理內存管理問題。
74.              正確地報告、處理和轉換錯誤
——什麼時候說什麼話:在檢查出並確認是錯誤時報告錯誤。在能夠正確處理錯誤的最近一層處理或者轉換每個錯誤。
75.              避免使用異常規範
——對異常規範說不:不要在函數中編寫異常規範,除非不得以而爲之。
STL:容器
76.              默認時使用vector。否則,選擇其他合適的容器
——使用“正確的容器”纔是正道:如果有充分的理由才使得某個特定容器類型,那就用好了,因爲我們心中有數:自己做出了正確的選擇。使用vector同樣如此:如果沒有充分理由,那就編寫vector,繼續前進,無需停頓,我們同樣心中有數:自己做出了正確的選擇。
——三個基本議題:
·編程時正確、簡單和清晰是第一位的。
·編程時只在必要時才考慮效率。
·儘可能編寫事務性的,強錯誤安全的代碼,而且不使用失效對象。
77.              用vector和string代替數組
——何必用貴重的明代花瓶玩耍雜技呢?不要使用C語言風格的數組、指針運算和內存管理原語操作實現數組抽象。使用vector或者string不僅更輕鬆,而且還有助於編寫更安全、伸縮性更好的軟件。
78.              使用vector(和string::c_str)與非C++API交換數據
——vector不會在轉換中迷失:vector和string::c_str是與非C++API通信的通道。但是不要將迭代器當作指針。要獲取vector<T>::iterator iter所引用的元素地址,應該使用&*iter。
79.              在容器中只存儲值和智能指針
——在容器中存儲值對象:容器假設它們所存放的是類似值的類型,包括值類型(直接存放),智能指針和迭代器。
——例子:
·auto_ptr
·異構容器
·非值類型的容器
·可選值
·索引容器
80.              用push_back代替其他擴展序列的方式
——儘可能使用push_back:如果不需要操心插入位置,就應該使用push_back在序列中添加元素。其他方法可能極慢而且不簡明。
81.              多用範圍操作,少用單元素操作
——順風順水無需漿(拉丁諺語):在序列容器中添加元素時,應該多用範圍操作(例如接受一對迭代器爲參數的insert形式),而不要連續用該操作的單元素形式。調用範圍操作通常更易於編寫,也更易於閱讀,而且比顯式循環的效率更高。
——例子:
·vector::insert
·範圍構造和賦值
82.              使用公認的慣用法真正地壓縮容量,真正地刪除元素
——使用有效減肥法:要真正地壓縮容器的多餘容量,應該使用“swap魔術”慣用法。要真正地刪除容器中的元素,應該使用eraser-remove慣用法。
STL:算法
83.              使用帶檢查STL實現
——安全第一:即使只在其中的一個編譯器平臺上可用,即使只能在發行前的測試中使用,也仍然要使用帶檢查的STL實現。
——STL錯誤:
·使用已失效的或未初始化的迭代器。
·傳遞越界索引。
·使用並非真是“範圍”的迭代器。
·傳遞無效的迭代器位置。
·使用無效順序。
84.              用算法調用代替手工編寫的循環
—— 明智地使用函數對象:對非常簡單的循環而言,手工編寫的循環有可能是最簡單也是最有效的解決方案。但是編寫算法調用代替手工編寫的循環,可以表達力更強、 維護性更好、更不易出錯,而且同樣高效。調用算法時,應該考慮編寫自定義的函數對象以封裝所需的邏輯。不要將參數幫定器(parameter- binder)和簡單的函數對象湊在一起,通常這會降低清晰性。還可以考慮嘗試[Boost]的Lambda庫,這個庫自動化了函數對象的編寫過程。
85.              使用正確的STL查找算法
—— 選擇查找方式應“恰到好處”——正確的查找方式應該使用STL(雖然比光速慢,但已經非常快了):本條款適用於在一個範圍內查找某個特定值,或者查找某個 值的位置(如果它處在範圍內的話)。查找無序範圍,應使用find/find_if或者count/count_if 。查找有序範圍,應使用lower_bound、upper_bound、equal-range或者(在少數情況下)binary_search(儘管 binary_search有一個通行的名字,但是選擇它通常並不一定正確)。
86.              使用正確的STL排序算法
——選擇排序方式應“恰到好處”:理解每個排序算法的作用,選擇能夠實現所需而開銷最低的算法。
87.              使謂詞成爲純函數
—— 保持謂詞純潔性:謂詞就是返回是或否(返回值通常爲bool類型)的函數對象。從數學的意義上來說,如果函數的結果只取決於其參數,則該函數就是一個純函 數。不要讓謂詞保存或訪問對其operator()結果有影響的狀態,包括成員狀態和全局狀態。應該使operator()謂詞的const成員函數。
88.              算法和比較器的參數應多用函數對象少用函數
——對象的適配性比函數好:應該向算法傳遞函數對象,而非函數。關聯容器的比較器比較是函數對象。函數對象的適配性好,而且與直覺相反,它們產生的代碼一般比函數更快。
89.              正確編寫函數對象
——成本要低,而且要可適配:將函數對象設計爲複製成本很低的值類型。儘可能地讓它們從unary_function或binary_function繼承,從而能夠適配。
類型安全
90.              避免使用類型分支,多使用多態
——切勿分支:避免通過對象類型分支來定製行爲。使用模板和虛函數,讓類型自己(而不是調用它們的代碼)來決定行爲。
91.              依賴類型,而非其表示方式
——不要企圖給對象拍X光片:不要對對象在內存中的準確表示方式做任何假設。相反,應該讓類型決定如何在內存中讀寫其對象。
92.              避免使用reinterpret_cast
—— 謊言總是站不住腳的:不要嘗試使用reinterpret_cast強制編譯器將某個類型 的內存表示重新解釋成另一種類型的對象。這違反了維護類型安全性的原則,尤其可怕的是,reinterpret_cast甚至不能保證是否能夠達到這一目 的,也無法保證其他功能。
——欺騙編譯器的人,最終將自食其果。
93.              避免對指針使用static_cast
——不要對動態對象的指針使用static_cast:安全的替代方法有很多,包括使用dynamic_cast,重構,乃至重新設計。
94.              避免強制轉換const
——莫因惡小而爲之:強制轉換const有時會導致未定義的行爲,即使合法,也是不良編程風格的主要表現。
95.              不要使用C風格的強制轉換
——年紀並不意味着智慧:C語言風格的強制轉換根據上下文具有不同(而且經常很危險)的語義,而所有這些都隱藏在相同的語法背後。用C++風格的強制轉換代替C風格的強制轉換有助於防範意想不到的錯誤。
96.              不要對非POD進行memcpy操作或者memcmp操作
——不要企圖給對象拍X光片:不要用memcpy或memcmp來複制或比較任何對象,除非有什麼對象的佈局就是原始內存。
97.              不要使用聯合重新解釋表示方式
——偷樑換柱也是一種欺騙:通過在union中寫入一個成員而度取另一個的濫用方式可以獲得“無需強制轉換的強制轉換”。這比起reinterpret_cast更陰險,也更難預測。
98.              不要使用可變長參數(…)
——省略會導致崩潰:省略號(…)是來自C語言的危險遺產。要避免使用可變長參數,應改用高級的C++結構和庫。
99.              不要使用失效對象。不要使用不安全函數
——不要使用失效藥:失效對象和老的但是不安全的函數會對程序的健康產生極大的破壞。
——失效對象主要有三種:
·已銷燬對象。
·語義失效對象。
·從來都有效的對象。
100.           不要多態地處理數組
——數組的可調整性很差:多態地處理數組是絕對的類型錯誤,而且編譯器有可能不會做出任何提示。不要掉入這一陷阱。
——指針可同時滿足兩種目的:
·一種是作爲別名(對象的小標識符)
·一種是作爲數組迭代器(以用指針運算遍歷對象數組)
——在接口中應該使用引用而不是指針。
/**************************************************************************
                                         The end.
**************************************************************************/
 
/*******************************************************************************
Scripts of C++ Coding Standards.
Description:This script is from the book called《C++ Coding Standards》.I loved it when I first saw the book..I held the book and I felt it very nice.Now it is,too.I found I was doing a significant thing,althought the authors is so learned that I realized I can’t follow them well in the last part. But I kept on reading the book and finished editing the script,edited by hackyeat from 2006-7-9 to2006-7-13.
Note:If you do not like the this comment,remove it and have a wonderful time.
Good luck!!!
*******************************************************************************/
 
發佈了135 篇原創文章 · 獲贊 34 · 訪問量 94萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章