天天看點

C++編碼規範與指導

目錄

  • 版權聲明
  • 概述
  • 針對 C 程式員的快速回顧
  • 文法高亮與字型
    • 字型
    • 文法高亮
  • 檔案結構
    • 檔案頭注釋
    • 頭檔案
    • 内聯函數定義檔案
    • 實作檔案
    • 檔案的組織結構
  • 命名規則
    • 類/結構
    • 函數
    • 變量
    • 常量
    • 枚舉、聯合、typedef
    • 宏、枚舉值
    • 名空間
  • 代碼風格與版式
    • 類/結構
    • 函數
    • 變量、常量
    • 枚舉、聯合、typedef
    • 名空間
    • 異常
    • 修改标記
  • 版本控制
  • 自動工具與文檔生成
  • 英文版
  • 關于本規範的貫徹實施
  • 術語表
  • 參考文獻
  • C++成長篇
  • 與我聯系

附件

  • 常用注釋一覽
  • 常用英文注釋一覽
  • 檔案頭例子
  • 頭檔案例子
  • 實作檔案例
  • 内聯函數定義檔案例子
  • 類/結構的風格與版式例子
  • 函數的風格與版式例子 

     

  • RTTI、虛函數和虛基類的實作方式、開銷分析和使用指導
  • C++異常機制的實作方式和開銷分析
  • 多處理器環境和線程同步的進階話題
  • C++0x(C++11)新特性點評
C++編碼規範與指導

版權聲明

本文檔版權歸作者所有。您可以以任意形式免費使用本文檔的任意部分,并且無需通知作者。作者對使用本文檔所造成的任何直接或者間接的損失不負任何責任。
C++編碼規範與指導

概述

對于任何工程項目來說,統一的施工标準都是保證工程品質的重要因素。堪稱當今人類最抽象、最複雜的工程——軟體工程,自然更加不能例外。

高品質、易維護的軟體開發離不開清晰嚴格的編碼規範。本文檔較長的描述C++軟體開發過程中的編碼規範。本規範也适用于所有在文檔中出現的源碼。

除了“文法高亮”部分,本文檔中的編碼規範都以:

規則(或建議) 解釋
的格式給出,其中強制性規則使用黑色,建議性規則使用灰色 。
C++編碼規範與指導

針對 C 程式員的快速回顧

本節旨在較高層面上快速回顧 C 與 C++ 的主要差別。專門針對 C 思想根深蒂固的老咖和經常需要在 C / C++ 項目間頻繁切換的 coder。C 與 C++ 的主要差別包括:
  • 空參函數聲明的預設參數類型為 void 而不是 int [編譯時]。
  • 強類型檢查和專門的類型轉換操作 [編譯時,除 dynamic_cast 操作]。
  • 名空間:用于歸類接口和子產品以及防止重名。名空間有自動向父級查詢比對和凱氏比對。名空間的一個副作用是名稱粉碎,可以用 extern "C" 聲明解決 [編譯時]。
  • 類:類将一個 C 結構體和與之相關的函數打包在一起,并且提供了編譯時的通路控制檢查,為了拟真内置類型,還提供了操作符重載 [編譯時]。
  • 類層次結構:類可以通過互相間的繼承和派生形成層次結構,派生類繼承了基類的資料結構和方法 [編譯時]。
  • 模闆:本質上是類型參數化。在編譯時生成 C++ 代碼的過程叫做模闆執行個體化,預設的執行個體化點在目前編譯單元第一次使用該模闆時,也可以通過顯式執行個體化來增加編譯速度。通過部分或完全的專門化可以為指定類型提供優化算法 [編譯時]。
  • 抽象類和虛方法:可以通過基類指針或引用通路的動态重載技術 [運作時]。
  • 多重繼承和虛基類:一個類可以有多個父親,為了避免層次結構中出現重複的基類,C++ 提供了虛基類 [運作時]。
  • 運作時類型資訊(RTTI):允許程式員在類層次結構中漫遊;完成動态轉換(向上、向下和交叉強制);擷取指定類型的 typeid 資訊,使用者可以以此為基礎實作反射、進階調試等各類功能。
  • 異常處理:本質上是一種安全的 longjmp 機制,在退棧期間能夠完成必要的對象析構動作。主要用于在發生錯誤時跳轉到相應的錯誤處理分支。
C++編碼規範與指導

文法高亮與字型

字型
C++編碼規範與指導

文字是資訊的載體;文字使我們能夠把個人的經驗和思想長久的儲存下來;文字使我們得以站在前人的肩膀上向前發展;文字的誕生标志着人類文明的開始……

扯的太離譜了?好吧,至少你應該承認:

  • 沒有文字就不可能出現計算機(先不管他是哪國字 
    C++編碼規範與指導
  • 沒有文字大家就不可能(也沒必要)學會如何寫程式
  • 在過去、現在和可見的将來,使用文字元号都是編寫計算機軟體的主要方式方法 
    C++編碼規範與指導

既然文字如此重要,它的長相自然會受到廣泛的關注。如今這個連MM都可以“千面”的年頭,字型的種類當然是數不勝數。

然而,前輩先賢們曾經用篆體教導偶們:

C++編碼規範與指導

。想讓大家讀到縮進、對齊正确一緻,而且不出現亂碼的源檔案,我們就要使用互相相容的字型。

字型規範如下:

使用等寬字型 由于非等寬字型在對齊等方面問題多多,任何情況下,源碼都必須使用等寬字型編輯和顯示。
每個制表符(TAB)的寬度為4個半角字元 不一緻的縮進寬度會導緻行與行之間的參差不齊,進而嚴重影響代碼的可讀性。
優先使用Fixedsys 在Windows平台中,應該優先使用字型:Fixedsys,這也是作業系統UI(所有的菜單、按鈕、标題欄、對話框等等)預設使用的字型。該字型的好處很多:
  • 相容性好:所有Windows平台都支援該字型
  • 顯示清晰:該字型為點陣字型,相對于矢量字型來說在顯示器中呈現的影像更為清晰。矢量字型雖然可以自由縮放,但這個功能對于純文字格式的程式源碼來說沒有任何實際作用。

    而且當顯示字号較小(12pt以下)時,矢量字型還有一些明顯的缺陷:

    • 文字的邊緣會有嚴重的凹凸感。
    • 一些筆畫的比例也會失調。
    • 開啟了柔化字型邊緣後,還會使文字顯得模糊不清。
    說句題外話,這也是Gnome和KDE等其它GUI環境不如Windows的一個重要方面 (2009年更新:現如今這些環境的界面和字型也不比 Windows 差了)。
  • 支援多語言:Fixedsys可以相容其它UNICODE等寬字型, 是以支援世界上幾乎所有的文字元号。這對編寫中文注釋是很友善的。

文法高亮
C++編碼規範與指導

幾乎所有的現代源碼編輯器均不同在程度上支援文法高亮顯示的功能。缤紛的色彩不但可以吸引MM們的目光,還可以在很大程度上幫助我們閱讀那些奧澀如咒語般的源代碼。

統一的文法高亮規則不僅能讓我們望色生意,還可以幫助我們閱讀沒有編碼規範,或者規範執行很爛的源碼。

所有在文檔中出現的代碼段均必須嚴格符合下表定義的文法高亮規範。在編輯源碼時,應該根據編輯器支援的自定義選項最大限度地滿足下表定義的高亮規範。

類型 顔色 舉例
注釋
C++編碼規範與指導
 R0;G128;B0(深綠)
// 注釋例子
關鍵字
C++編碼規範與指導
 R0;G0;B255(藍)
typedef, int, dynamic_cast class ...
類、結構、聯合、枚舉等其它自定義類型
C++編碼規範與指導
 R0;G0;B255(藍)
class CMyClass, enum ERRTYPE, typedef int CODE ...
名空間
C++編碼規範與指導
 R0;G0;B255(藍)
namespace BaiY
數字
C++編碼規範與指導
 R255;G0;B0(紅)
012 119u 0xff ...
字元、字元串
C++編碼規範與指導
 R0;G128;B128(深藍綠)
"string", 'c ...
宏定義、枚舉值
C++編碼規範與指導
 R255;G128;B0(橙黃)
#define UNICODE, enum { RED, GREEN, BLUE };
操作符
C++編碼規範與指導
 R136;G0;B0(棕色)
< > , = + - * / ; { } ( ) [ ] ...
方法/函數
C++編碼規範與指導
 R136;G0;B0(棕色)
MyFunc()
變量
C++編碼規範與指導
 R128;G128;B128(中灰色)
int nMyVar;
背景
C++編碼規範與指導
 R255;G255;B255(白色)
其它
C++編碼規範與指導
 R0;G0;B0(黑色)
other things(通常是一個錯誤)
C++編碼規範與指導

檔案結構

檔案頭注釋
C++編碼規範與指導

所有C++的源檔案均必須包含一個規範的檔案頭,檔案頭包含了該檔案的名稱、功能概述、作者、版權和版本曆史資訊等内容。标準檔案頭的格式為:
如果該檔案有其它需要說明的地方,還可以專門為此擴充一節,節與節之間用長度為80的“=”帶分割:

每行注釋的長度都不應該超過80個半角字元。還要注意縮進和對齊,以利閱讀。

注意:将多線程和異常時安全性描述放在檔案頭,而不是類或者函數注釋中,是為了展現以下設計思想:同一個子產品中的界面,其各方面的操作方式和使用風格應該盡量保持一緻。

關于檔案頭的完整例子,請參見:檔案頭例子

關于檔案頭的模闆,請參見:檔案頭注釋模闆

頭檔案
C++編碼規範與指導

頭檔案通常由以下幾部分組成:
檔案頭注釋 每個頭檔案,無論是内部的還是外部的,都應該由一個規範的檔案頭注釋作為開始。
預處理塊 為了防止頭檔案被重複引用,應當用ifndef/define/endif結構産生預處理塊。
函數和類/結構的聲明等 聲明子產品的接口
需要包含的内聯函數定義檔案(如果有的話) 如果類中的内聯函數較多,或者一個頭檔案中包含多個類的定義(不推薦),可以将所有内聯函數定義放入一個單獨的内聯函數定義檔案中,并在類聲明之後用“#include”指令把它包含進來。
頭檔案的編碼規則:
引用檔案的格式

用 #include <filename.h> 格式來引用标準庫和系統庫的頭檔案(編譯器将從标準庫目錄開始搜尋)。

用 #include "filename.h" 格式來引用目前工程中的頭檔案(編譯器将從該檔案所在目錄開始搜尋)。

分割多組接口(如果有的話) 如果在一個頭件中定義了多個類或者多組接口(不推薦),為了便于浏覽,應該在每個類/每組接口間使用分割帶把它們互相分開。
關于頭檔案的完整例子,請參見:頭檔案例子

内聯函數定義檔案
C++編碼規範與指導

如上所述,在内聯函數較多的情況下,為了避免頭檔案過長、版面混亂,可以将所有的内聯函數定義移到一個單獨的檔案中去,然後再用#include指令将它包含到類聲明的後面。這樣的檔案稱為一個内聯函數定義檔案。

按照慣例,應該将這個檔案命名為“filename.inl”,其中“filename”與相應的頭檔案和實作檔案相同。

内聯函數定義檔案由以下幾部分組成:

檔案頭注釋 每内聯函數定義檔案都應該由一個規範的檔案頭注釋作為開始
内聯函數定義 内聯函數的實作體
内聯函數定義檔案的編碼規則:
分割多組接口(如果有的話) 如果在一個内聯函數定義檔案中定義了多個類或者多組接口的内聯函數(不推薦),必須在每個類/每組接口間使用分割帶把它們互相分開。
檔案組成中為什麼沒有預處理塊? 與頭檔案不同,内聯函數定義檔案通常不需要定義預處理塊,這是因為他們總是被包含在與其相應的頭檔案預處理塊内。
關于内聯函數定義檔案的完整例子,請參見:内聯函數定義檔案例子

實作檔案
C++編碼規範與指導

實作檔案包含所有資料和代碼的實作體。實作檔案的格式為:
檔案頭注釋 每個實作檔案都應該由一個規範的檔案頭注釋作為開始
對配套頭檔案的引用 引用聲明了此檔案實作的類、函數及資料的頭檔案
對一些僅用于實作的頭檔案的引用(如果有的話) 将僅與實作相關的接口包含在實作檔案裡(而不是頭檔案中)是一個非常好的程式設計習慣。這樣可以有效地屏蔽不應該暴露的實作細節,将實作改變對其它子產品的影響降低到最少 。
程式的實作體 資料和函數的定義
實作檔案的編碼規則:
分割每個部分 在本地(靜态)定義和外部定義間,以及不同接口或不同類的實作之間,應使用分割帶互相分開。
關于實作檔案的完整例子,請參見:實作檔案例子

檔案的組織結構
C++編碼規範與指導

由于項目性質、規模上存在着差異,不同項目間的檔案組織形式差别很大。但檔案、目錄組織的基本原則應當是一緻的:使外部接口與内部實作盡量分離;盡可能清晰地表達軟體的層次結構……

為此提供兩組典型項目的檔案組織結構範例作為參考:

功能子產品/庫的檔案組織形式

顯而易見,編寫功能子產品和庫的主要目的是為其它子產品提供一套完成特定功能的API,這類項目的檔案組織結構通常如下圖所示:
C++編碼規範與指導
其中:
contrib 目前項目所依賴的所有第三方軟體,可以按類别分設子目錄。
doc 項目文檔
include 聲明外部接口的所有頭檔案和内聯定義檔案。
lib 編譯好的二進制庫檔案,可以按編譯器、平台分設子目錄。
makefile 用于編譯項目的makefile檔案和project檔案等。可以按編譯器、平台分設子目錄。
src 所有實作檔案和聲明内部接口的頭檔案、内聯定義檔案。可按功能劃分;支援編譯器、平台等類别分設子目錄。
test 存放測試用代碼的目錄。

應用程式的檔案組織形式

與功能子產品不同,應用程式是一個傳遞給最終用于使用的、可以獨立運作并提供完整功能的軟體産品,它通常不提供程式設計接口,應用程式的典型檔案組織形式如下圖所示:
C++編碼規範與指導
contrib 目前項目所依賴的所有第三方軟體,可以按類别分設子目錄。
doc 項目文檔
makefile 用于編譯項目的makefile檔案和project檔案等。可以按編譯器、平台分設子目錄。
setup 安裝程式,以及制作安裝程式所需要的項目檔案和角本。
src 所有源檔案。可按功能劃分;支援編譯器、平台等類别分設子目錄。
test 存放測試用代碼的目錄。
C++編碼規範與指導

命名規則

如果想要有效的管理一個稍微複雜一點的體系,針對其中事物的一套統一、帶層次結構、清晰明了的命名準則就是必不可少而且非常好用的工具。

活躍在生物學、化學、軍隊、監獄、黑社會、恐怖組織等各個領域内的大量有識先輩們都曾經無數次地以實際行動證明了以上公理的正确性。除了上帝(設它可以改變世間萬物的秩序)以外,相信沒人有實力對它不屑一顧 

C++編碼規範與指導

在軟體開發這一高度抽象而且十分複雜的活動中,命名規則的重要性更顯得尤為突出。一套定義良好并且完整的、在整個項目中統一使用的命名規範将大大提升源代碼的可讀性和軟體的可維護性。

在引入細節之前,先說明一下命名規範的整體原則:

同一性 在編寫一個子子產品或派生類的時候,要遵循其基類或整體子產品的命名風格,保持命名風格在整個子產品中的同一性。
辨別符組成 辨別符采用英文單詞或其組合,應當直覺且可以拼讀,可望文知意,用詞應當準确。
最小化長度 && 最大化資訊量原則 在保持一個辨別符意思明确的同時,應當盡量縮短其長度。
避免過于相似 不要出現僅靠大小寫區分的相似的辨別符,例如“i”與“I”,“function”與“Function”等等。
避免在不同級别的作用域中重名 程式中不要出現名字完全相同的局部變量和全局變量,盡管兩者的作用域不同而不會發生文法錯誤,但容易使人誤解。
正确命名具有互斥意義的辨別符 用正确的反義詞組命名具有互斥意義的辨別符,如:"nMinValue" 和 "nMaxValue","GetName()" 和 "SetName()" ....
避免名字中出現數字編号 盡量避免名字中出現數字編号,如Value1,Value2等,除非邏輯上的确需要編号。這是為了防止程式員偷懶,不肯為命名動腦筋而導緻産生無意義的名字(因為用數字編号最省事)。

類/結構
C++編碼規範與指導

除了異常類等個别情況(不希望被使用者看作一個普通的、正常的類之情況)外,C++類/結構的命名應該遵循以下準則:
C++類/結構的命名

類的名稱都要以大寫字母“C”開頭,後跟一個或多個單詞。為便于界定,每個單詞的首字母要大寫。

特别地,由于界面與其它類概念上的巨大差别,規定界面類要以大寫字母“I”開頭。界面類描述一個服務(一組被命名的操作集合),在C++中,界面與其它類間的最大差別在于,界面類中不包含任何資料結構(屬性),也不包括任何具體的操作和實作,界面類通常僅包含一組純虛函數的聲明而不包含任何實作和資料。在一些其它語言中,一個界面也被稱作一個接口及其實作契約。

另一個與接口相似的概念是類型,類型與接口的不同點在于,類型可以包含部分接口的實作或包含一些接口預設的或不完整的實作,一個類型也可以包含一些屬性。規定類型類要以大寫字母“T”開頭。例如:轎車類型 "TCar"、線程類型 "TThread" 等等。在C++種,類型類也叫做結點類。

在現實世界中,類型和界面的差別往往比較微妙。在真實代碼中,有些類除了包含純虛函數以外,也可能同時包含幾個帶簡單預設實作的普通虛函數。例如:某個類中可能包含一個(非純虛)虛方法 IsLoadable,并定義了該方法的預設實作:return false;。我們不難找出很多類似的例子。

以下是一些類型和界面的界定政策:

  • 如果一個類中包含靜态成員,則一定不是界面
  • 如果一個類中包含屬性,則一定不是界面
  • 如果一個類中包含非虛方法,則一定不是界面
  • 如果一個類中包含非公有成員,則一定不是界面
  • 如果一個類中包含模闆方法,則一定不是界面。

    這裡的模闆方法是指那些調用了該類中其它虛函數的成員,這樣的方法通常用于實作針對某種應用的算法架構,這顯然超出了界面的範疇。

    在C++中,模闆方法的另一個意思通常指使用函數模闆的成員,由于C++函數模闆隻能是非虛的,是以包含這種方法的類也一定不是界面。

  • 通常定義那些不十分明确的接口時,先将其指定為一個界面,必要時再把它提升為一個類型。

模闆類 的命名規範與實體類相同。

為了更好地表示代碼段之間的相關性和增加程式的可讀性。我們經常會把一段僅在某個函數内反複使用的代碼片段封裝為一個函數對象,并定義在這個函數體内。對于這類實作功能簡單,并且主要通過 operator() 來使用的類/結構,其名稱應當以大寫字母“FO”開頭,如:"FONameChecker" 等。

推薦的組成形式 類的命名推薦用"名詞"或"形容詞+名詞"的形式,例如:"CAnalyzer", "CFastVector", "IUnknown", "IDBWriter", "TTimer", "TThread" ....
不同于C++類的概念,傳統的C結構體隻是一種将一組資料捆綁在一起的方式。傳統C結構體的命名規則為:
傳統C結構體的命名 傳統C結構體的名稱全部由大寫字母組成,單詞間使用下劃線界定,例如:"SERVICE_STATUS", "DRIVER_INFO" ....

函數
C++編碼規範與指導

函數的命名 函數的名稱由一個或多個單詞組成。為便于界定,每個單詞的首字母要大寫。
推薦的組成形式 函數名應當使用"動詞"或者"動詞+名詞"(動賓詞組)的形式。例如:"GetName()", "SetValue()", "Erase()", "Reserve()" ....
保護成員函數 保護成員函數的開頭應當加上一個下劃線“_”以示差別,例如:"_SetState()" ....
私有成員函數 類似地,私有成員函數的開頭應當加上兩個下劃線“__”,例如:"__DestroyImp()" ....
私有成員函數的層次結構表示

通常來說,在一個類中,公有方法、保護方法和私有方法所完成的任務總是呈現一種逐級依次細化的層次結構(意即:保護方法所實作的功能通常比該類中的公有方法更為細小瑣碎;類似地,私有方法的功能也比其保護方法更具原子性)。

是以,對于遵循以上規則,并且功能較為複雜的類,在按照“公有、保護、私有”的三級形式劃分以後,如果其私有成員中仍然存在明顯不同的功能粒度,則可以通過追加更多下劃線字首的形式予以表示。

例如:由三個下劃線開頭的私有方法“___PushCdr”就要比同一類中,僅由兩個下劃線開頭的私有方法“__MergeConCall”所完成的功能粒度更細小、更瑣碎;而四個下劃線開頭的“____CalcCompensate”則比“___PushCdr”完成的功能 更具原子性。

如果發現類中的功能層數太多(從公有方法到最“原子”的私有方法間,一般不應該超過 7 層),那通常反應一個不良的設計。此時請檢查這個類的功能是否過于臃腫,已使接口顯得不太清晰。另外一個常見的問題是将無需通路該類中 私有或保護成員的功能定義成了方法。第一個問題可以通過重新劃分類層次結構或将一個類分裂為多個類等方法解決。對于第二個問題,由于這些方法無需通路 受限成員,大多數時候都可以把它們轉變成局部函數(放在無名空間或使用“static”字首定義)。

成員函數的下劃線字尾命名

對一些本應該作為保護或私有成員的函數,由于設計方面的其它考慮(例如:靈活性、功能等方面)将其提升為公有成員的,應該在其後面添加與其原本通路控制級别相應的下劃線字尾。

另外,對于其它不推薦直接使用的成員函數(例如:會引起相容性或可移植性方面問題的函數),也應當在其後面加相應下劃線提示。

例如:"ioctl_()", "SetSysOpt_()", "GetSysOpt_()", "PreParser__()" ....

回調和事件處理函數 回調和事件處理函數習慣以單詞“On”開頭。例如:"_OnTimer()", "OnExit()" ....
虛函數 回調函數以外的虛函數習慣以“Do”開頭,如:"DoRefresh()", "_DoEncryption()" ....

變量
C++編碼規範與指導

變量應該是程式中使用最多的辨別符了,變量的命名規範可能是一套C++命名準則中最重要的部分:
變量的命名

變量名由作用域字首+類型字首+一個或多個單詞組成。為便于界定,每個單詞的首字母要大寫。

對于某些用途簡單明了的局部變量,也可以使用簡化的方式,如:i, j, k, x, y, z ....

作用域字首 作用域字首标明一個變量的可見範圍。作用域可以有如下幾種:
字首 說明
局部變量
m_ 類的成員變量(member)
sm_ 類的靜态成員變量(static member)
s_ 靜态變量(static)
g_ 外部全局變量(global)
sg_ 靜态全局變量(static global)
gg_ 程序或動态連結庫間共享的全局變量(global global)

除非不得已,否則應該盡可能少使用全局變量。

關于全局變量和局部靜态變量的依賴性問題和初始化時的線程安全性問題,請參考:多處理器環境和線程同步的進階話題 一節

類型字首 類型字首标明一個變量的類型,可以有如下幾種:
字首 說明
n 整型和位域變量(number)
e 枚舉型變量(enumeration)
c 字元型變量(char)
b 布爾型變量(bool)
f 浮點型變量(float)
p 指針型變量和疊代子(pointer)
pfn 指向函數的指針變量或指向函數對象的指針(pointer of function)
pm 指向成員的指針(pointer of member)
r 引用(reference),此字首對于常引用(const reference)來說可以省略
g 數組(grid)
fo 函數對象(Function Object)
i

類的執行個體(instance)

對于經常用到的類,也可以定義一些專門的字首,如:std::string和std::wstring類的字首可以定義為"st",std::vector類的字首可以定義為"v"等等。

類型字首可以組合使用,例如"gc"表示字元數組,"ppn"表示指向整型的指針的指針等等。
數值字首的特别記法 以“n”作為所有整形字首是由于大多數情況下,編寫程式時不需要過多考慮整形的寬度,但在某些場合中,整形寬度是需要特别注意并且仔細加以區分的,這時可使用如下記法代替“n”字首:
字首 說明
b 位元組(8bit,byte)
w 字(16bit,word)
dw 雙字(32bit,double word)
qw -或- nn 四字(64bit,quad word)
bf 位域(bit field)
對浮點型變量也有類似記法如下:
字首 說明
f 單精度浮點(32bit,float)
d 雙精度浮點(64bit,double)
ld 擴充精度浮點(80bit,long double)
推薦的組成形式 變量的名字應當使用"名詞"或者"形容詞+名詞"。例如:"nCode", "m_nState","nMaxWidth" ....

常量
C++編碼規範與指導

C++中引入了對常量的支援,常量的命名規則如下:
常量的命名

常量名由類型字首+全大寫字母組成,單詞間通過下劃線來界定,如:cDELIMITER, nMAX_BUFFER ....

類型字首的定義與變量命名規則 中的相同。

枚舉、聯合、typedef
C++編碼規範與指導

枚舉、聯合及typedef語句都是定義新類型的簡單手段,它們的命名規則為:
枚舉、聯合的命名 由枚舉、聯合語句定義的類型名由全大寫字母組成,單詞間通過下劃線來界定,如:FAR_PROC, ERROR_TYPE ....
typedef的命名 通常情況下,typedef語句定義的類型名,其命名規範與枚舉及聯合語句相同,也采用大寫字母加下劃線的原則。但是在定義一個模闆類執行個體的别名時,為清晰起見,可以考慮酌情使用類的命名原則,例如:

typedef CWriter< CSysFile > CSysFileWriter;

typedef std::vector< int > VINT;

宏、枚舉值
C++編碼規範與指導

宏、枚舉值的命名 宏和枚舉值由全大寫字母組成,單詞間通過下劃線來界定,如:ERROR_UNKNOWN, OP_STOP ....

名空間
C++編碼規範與指導

C++名空間是“類”概念的一種退化(大體相當于隻包含靜态成員且不能執行個體化的類)。它的引入為辨別符名稱提供了更好的層次結構,使辨別符看起來更加直覺簡捷,同時大大降低了名字沖突的可能性。

名空間的命名規則包括:

名空間的命名

名空間的名稱不應該過長,通常都使用縮寫的形式來命名。

例如,一個圖形庫可以将其所有外部接口存放在名空間"GLIB"中,但是将其換成"GRAPHIC_LIBRARY"就不大合适。

如果碰到較長的名空間,為了簡化程式書寫,可以使用:

namespace new_name = old_long_name;
語句為其定義一個較短的别名。
C++編碼規範與指導

代碼風格與版式

代碼風格的重要性怎麼強調都不過分。一段稍長一點的無格式代碼基本上就是不可讀的。

先來看一下這方面的整體原則:

空行的使用 空行起着分隔程式段落的作用。空行得體(不過多也不過少)将使程式的布局更加清晰。空行不會浪費記憶體,雖然列印含有空行的程式是會多消耗一些紙張,但是值得。是以不要舍不得用空行。
  • 在每個類聲明之後、每個函數定義結束之後都要加2行空行。
  • 在一個函數體内,邏揖上密切相關的語句之間不加空行,其它地方應加空行分隔。
語句與代碼行
  • 一行代碼隻做一件事情,如隻定義一個變量,或隻寫一條語句。這樣的代碼容易閱讀,并且友善于寫注釋。
  • "if"、"for"、"while"、"do"、"try"、"catch" 等語句自占一行,執行語句不得緊跟其後。不論執行語句有多少都要加 "{ }" 。這樣可以防止書寫和修改代碼時出現失誤。
縮進和對齊
  • 程式的分界符 "{" 和 "}" 應獨占一行并且位于同一列,同時與引用它們的語句左對齊。
  • "{ }" 之内的代碼塊在 "{" 右邊一個制表符(4個半角空格符)處左對齊。如果出現嵌套的 "{ }",則使用縮進對齊。
  • 如果一條語句會對其後的多條語句産生影響的話,應該隻對該語句做半縮進(2個半角空格符),以突出該語句。
例如:

void  Function(int x) {   CSessionLock iLock(mxLock);     for (初始化; 終止條件; 更新)     {         // ...     }     try     {         // ...     }     catch (const exception& err)     {         // ...     }     catch (...)     {         // ...     }     // ... }

最大長度 代碼行最大長度宜控制在70至80個字元以内。代碼行不要過長,否則眼睛看不過來,也不便于列印(2009年更新:随着GUI開發環境和高分寬屏的普及,此規則可以視情況适當放寬)。
長行拆分

長表達式要在低優先級操作符處拆分成新行,操作符放在新行之首(以便突出操作符)。拆分出的新行要進行适當的縮進,使排版整齊,語句可讀。

例如:

if ((very_longer_variable1 >= very_longer_variable2)     && (very_longer_variable3 <= very_longer_variable4)     && (very_longer_variable5 <= very_longer_variable6)) {     DoSomething(); }

空格的使用
  • 關鍵字之後要留白格。象 "const"、"virtual"、"inline"、"case" 等關鍵字之後至少要留一個空格,否則無法辨析關鍵字。象 "if"、"for"、"while"、"catch" 等關鍵字之後應留一個空格再跟左括号 "(",以突出關鍵字。
  • 函數名之後不要留白格,緊跟左括号 "(",以與關鍵字差別。
  • "(" 向後緊跟。而 ")"、","、";" 向前緊跟,緊跟處不留白格。
  • "," 之後要留白格,如 Function(x, y, z)。如果 ";" 不是一行的結束符号,其後要留白格,如 for (initialization; condition; update)。
  • 指派操作符、比較操作符、算術操作符、邏輯操作符、位域操作符,如"="、"+=" ">="、"<="、"+"、"*"、"%"、"&&"、"||"、"<<", "^" 等二進制操作符的前後應當加空格。
  • 一進制操作符如 "!"、"~"、"++"、"--"、"&"(位址運算符)等前後不加空格。
  • 象"[]"、"."、"->"這類操作符前後不加空格。
  • 對于表達式比較長的 for、do、while、switch 語句和 if 語句,為了緊湊起見可以适當地去掉一些空格,如 for (i=0; i<10; i++) 和 if ((a<=b) && (c<=d)) 等。
例如:

void Func1(int x, int y, int z);    // 良好的風格 void Func1 (int x,int y,int z);     // 不良的風格 // =========================================================== if (year >= 2000)         // 良好的風格 if(year>=2000)            // 不良的風格 if ((a>=b) && (c<=d))     // 良好的風格 if(a>=b&&c<=d)            // 不良的風格 // =========================================================== for (i=0; i<10; i++)      // 良好的風格 for(i=0;i<10;i++)         // 不良的風格 for (i = 0; I < 10; i ++) // 過多的空格 // =========================================================== x = a < b ? a : b;        // 良好的風格 x=a<b?a:b;                // 不好的風格 // =========================================================== int* x = &y;              // 良好的風格  int * x = & y;            // 不良的風格  // =========================================================== array[5] = 0;             // 不要寫成 array [ 5 ] = 0; a.Function();             // 不要寫成 a . Function(); b->Function();            // 不要寫成 b -> Function();

修飾符的位置

為便于了解,應當将修飾符 "*" 和 "&" 緊靠資料類型。

例如:

char* name;

int* x;

int  y;    // 為避免y被誤解為指針,這裡必須分行寫。

int* Function(void* p);

參見:變量、常量的風格與版式 -> 指針或引用類型的定義和聲明
注釋
  • 注釋的位置應與被描述的代碼相鄰,可以放在代碼的上方或右方,不可放在下方。
  • 邊寫代碼邊注釋,修改代碼同時修改相應的注釋,以保證注釋與代碼的一緻性。不再有用的注釋要删除。
  • 注釋應當準确、易懂,防止注釋有二義性。錯誤的注釋不但無益反而有害。
  • 當代碼比較長,特别是有多重嵌套時,應當在一些段落的結束處加注釋,便于閱讀。
與常量的比較

在與宏、常量進行 "==", "!=", ">=", "<=" 等比較運算時,應當将常量寫在運算符左邊,而變量寫在運算符右邊。這樣可以避免因為偶然寫錯把比較運算變成了指派運算的問題。

例如:

if (NULL == p)  // 如果把 "==" 錯打成 "=",編譯器就會報錯

{

    // ...

}

為增強代碼的可讀性而定義的宏 以下預定義宏對程式的編譯沒有任何影響,隻為了增加代碼的可讀性:
說明
NOTE 需要注意的代碼
TODO 尚未實作的接口、類、算法等
UNDONE 已取消的接口、類、算法等
FOR_DBG 标記為調試友善而臨時增加的代碼
OK 僅用于調試的标記
例如:

TODO class CMyClass;

TODO void Function(void);

FOR_DBG cout << "...";

類/結構
C++編碼規範與指導

類是C++中最重要也是使用頻率最高的新特性之一。類的版式好壞将極大地影響代碼品質。
注釋頭與類聲明

與檔案一樣,每個類應當有一個注釋頭用來說明該類的各個方面。

類聲明換行緊跟在注釋頭後面,"class" 關鍵字由行首開始書寫,後跟類名稱。界定符 "{" 和 "};" 應獨占一行,并與 "class" 關鍵字左對齊。

class CXXX

{

    // ...

};

對于功能明顯的簡單類(接口小于10個),也可以使用簡單的單行注釋頭:

//! <簡要說明該類所完成的功能>

class CXXX

{

    // ...

};

繼承 基類直接跟在類名稱之後,不換行,通路說明符(public, private, 或 protected)不可省略。如:

class CXXX : public CAAA, private CBBB

{

    // ...

};

以行為為中心

沒人喜歡上來就看到一大堆私有資料,大多數使用者關心的是類的接口與 它提供的服務,而不是其實作細節。

是以應當将公有的定義和成員放在類聲明的最前面,保護的放在中間,而私有的擺在最後。

通路說明符 通路說明符(public, private, 或 protected)應該獨占一行,并與類聲明中的‘class’關鍵字左對齊。
類成員的聲明版式

對于比較複雜(成員多于20個)的類,其成員必須分類聲明。

每類成員的聲明由通路說明符(public, private, 或 protected)+ 全行注釋開始。注釋不滿全行(80個半角字元)的,由 "/" 字元補齊,最後一個 "/" 字元與注釋間要留一個半角空格符。

如果一類聲明中有很多組功能不同的成員,還應該用分組注釋将其分組。分組注釋也要與 "class" 關鍵字對齊。

每個成員的聲明都應該由 "class" 關鍵字開始向右縮進一個制表符(4個半角空格符),成員之間左對齊。

例如:

class CXXX

{

public:

/// 類型定義

    typedef vector<string> VSTR;

public:

/ 構造、析構、初始化

    CXXX();

    ~CXXX();

public:

/// 公用方法

// [[ 功能組1

    void Function1(void) const;

    long Function2(IN int n);

// ]] 功能組1

// [[ 功能組2

    void Function3(void) const;

    bool Function4(OUT int& n);

// ]] 功能組2

private:

/// 屬性

    // ...

private:

/ 禁用的方法

    // 禁止複制

    CXXX(IN const CXXX& rhs);

    CXXX& operator=(IN const CXXX& rhs);

};

正确地使用const和mutable

把不改變對象邏輯狀态的成員都标記為 const 成員不僅有利于使用者對成員的了解,更可以最大化對象使用方式的靈活性及合理性(比如通過 const 指針或 const 引用的形式傳遞一個對象)。

如果某個屬性的改變并不影響該對象邏輯上的狀态,而且這個屬性需要在 const 方法中被改變,則該屬性應該标記為 "mutable"。

例如:

class CString

{

public:

    //! 查找一個子串,find() 不會改變字元串的值 ,是以為 const 函數

    int find(IN const CString& str) const;

    // ...

private:

    // 最後一次錯誤值,改動這個值不會影響對象的邏輯狀态,

    // 像 find() 這樣的 const 函數也可能修改這個值

    mutable int m_nLastError; 

    // ...

};

也就是說,應當盡量使所有邏輯上隻讀的操作成為 const 方法,然後使用 mutable 解決那些存在邏輯沖突的屬性。
嵌套的類聲明 在相應的邏輯關系确實存在時,類聲明可以嵌套。嵌套類可以使用簡單的單行注釋頭:

// ...

class CXXX

{

    //! 嵌套類說明

    class CYYY

    {

        // ...

    };

};

初始化清單

應當盡可能通過構造函數的初始化清單來初始化成員和基類。初始化清單至少獨占一行,并且與構造函數的定義保持一個制表符(4個半角空格)的縮進。

例如:

CXXX::CXXXX(IN int nA, IN bool bB)

    : m_nA(nA), m_bB(bB)

{

    // ...

};

初始化清單的書寫順序應當與對象的構造順序一緻,即:先按照聲明順序寫基類初始化,再按照聲明順序寫成員初始化。

如果一個成員 "a" 需要使用另一個成員 "b" 來初始化,則 "b" 必須在 "a" 之前聲明,否則将會産生運作時錯誤(有些編譯器會給出警告)。

例如:

// ... class CXXXX : public CAA, public CBB {     // ...     CYY m_iA;     CZZ m_iB;  // m_iA 必須在 m_iB 之前聲明 }; CXXX::CXXXX(IN int nA, IN int nB, IN bool bC)     : CAA(nA), CBB(nB), m_iA(bC), m_iB(m_iA) // 先基類,後成員,                                              // 分别按照聲明順序書寫 {     // ... };

内聯函數的實作體 定義在類聲明之中的函數将自動成為内聯函數。但為了使類的聲明更為清晰明了,應盡量避免直接在聲明中直接定義成員函數的程式設計風格。鼓勵使用 "inline" 關鍵字将内聯函數放在類聲明的外部定義。

關于類聲明的例子,請參見:類/結構的風格與版式例子

關于類聲明的模闆,請參見:類聲明模闆

函數
C++編碼規範與指導

函數是程式執行的最小機關,任何一個有效的C/C++程式都少不了函數。
函數原型 函數原型的格式為:

[存儲類] 傳回值類型

[名空間或類::]函數名(參數清單) [const說明符] [異常過濾器]

例如:

static inline void

Function1(void)

int

CSem::Function2(IN const char* pcName) const throw(Exp)

其中:
  • 以 "[ ]" 包覆的為可選項目。
  • 除了構造/析構函數及類型轉換操作符外,"傳回值類型 " 和 "參數清單" 項不可省略(可以為 "void")。
  • "const說明符" 僅用于成員函數中 。
  • "存儲類 ", "參數清單" 和 "異常過濾器" 的說明見下文 。
函數聲明 函數聲明的格式為:

//! 函數功能簡單說明(可選)

函數原型;

例如:

//! 執行某某操作

static void

Function(void);

函數聲明和其它代碼間要有空行分割。

聲明類的成員函數時,為了緊湊,傳回值類型和函數名之間不用換行,也可以适當減少聲明間的空行。

函數定義 函數定義使用如下格式:

函數原型

{

    // ...

}

對于傳回值、參數意義都很明确的簡單函數(代碼不超過20行),也可以使用單行函數頭:

//! 函數實作功能

函數原型

{

    // ...

}

函數定義和其它代碼之間至少分開2行空行。
參數描述宏 以下預定義宏對程式的編譯沒有任何影響,隻為了增強對參數的了解:
說明
IN 輸入參數。
OUT 輸出參數。
DUMMY 啞元參數-不使用參數的值,僅為幫助函數重載解析等目的而設定的參數。
OPTIONAL 可選參數-通常指可以為NULL的指針參數,帶預設值的參數不需要這樣标明。
RESERVED 保留參數-這個參數目前未被支援,留待以後擴充;或者該參數為内部使用,使用者無需關心。
OWNER 獲得參數的所有權,調用者不再負責銷毀實參指定的對象;如果用來修飾傳回值,則表示調用者獲得傳回值的所有權,并負責将其銷毀。
UNUSED 标明這個參數在此版本中已不再使用。
CHANGED 參數類型或用途與較早版本相比發生了變化。
ADDED 新增的參數。
NOTE 需要注意的參數-參數意義發生變化或者與習慣用法不同。
WRKBUF 工作緩沖區-為避免頻繁配置設定臨時資源而傳入的臨時工作區。
DEFERRED 表示指定的參數會被延後使用,調用者在目前調用傳回後仍然要保證該參數有效,直到事先在接口中約定的,某個未來的時間點。
TRANSIENT 表示參數指定的對象隻能在調用傳回前使用,調用傳回後該對象可能失效。是以不能保留此對象供以後使用。
其中:
  • 除了空參數 "void" 和啞元參數以外,每個參數左側都必須有 "IN" 和/或 "OUT" 修飾。
  • 既輸入又輸出的參數應記為:"IN OUT",而不是 "OUT IN"。
  • IN/OUT的左側還可以根據需要加入一個或多個上表中列出的其它宏 。

參數描述宏的使用思想是:隻要一個描述宏可以用在指定參數上(即:對這個參數來說,用這個描述宏修飾它是貼切的),那麼就應當使用它。

也就是說,應該把能用的描述宏都用上,以期盡量具體地描述一個參數的作用和用法等資訊。

參數清單 參數清單的格式為:
參數描述宏1 參數類型1 參數1, 參數描述宏2 參數類型2 參數2, ...
例如:

IN const int nCode, OUT string& nName

OWNER IN CDatabase* piDB, OPTIONAL IN OUT int* pnRecordCount = NULL

IN OUT string& stRuleList, RESERVED IN int nOperate = 0

...

其中:
  • "參數描述宏" 見上文
  • 參數命名規範與變量的命名規範 相同
存儲類 "extern", "static", "inline" 等函數存儲類說明應該在聲明和定義中一緻并且顯式地使用。不允許隐式地使用一個類型聲明,也不允許一個類型聲明僅存在于函數的聲明或定義中。
成員函數的存儲類

由于C++語言的限制,類中成員函數的 "static", "virtual", "explicit" 等存儲類說明不允許出現在函數定義中。

但是為了明确起見,這些存儲類應以注釋的形式在相應的成員定義中給出。

例如:

 CThread::EXITCODE

CSrvCtl::CWrkTrd::Entry(void)

{

    // ...

}

 inline void 

stringEx::regex_free(IN OUT void*& pRegEx)

{

    // ...

}

特别地, 對于在類聲明中直接實作的方法, 可以省略其 "inline" 關鍵字 。而在内聯(.inl)檔案中實作的 inline 方法則不能省略。這是因為 inline linkage 是在聲明時起效的。
預設參數

類似地,參數的預設值隻能出現在函數聲明中,但是為了明确起見,這些預設值應以注釋的形式在定義中給出。

例如:

bool stringEx::regex_find(OUT VREGEXRESULT& vResult,                      IN  stringEx      stRegEx,                       IN  size_t        nIndex        ,                      IN  size_t        nStartPos     ,                      IN  bool          bNoCase       ,                       IN  bool          bNewLine      ,                      IN  bool          bExtended     ,                      IN  bool          bNotBOL       ,                      IN  bool          bNotEOL       ,                      IN  bool          bUsePerlStyle ) const {     // ... }

異常過濾器

對于任何可能抛出異常的函數,必須在其聲明和定義中顯式地指定異常過濾器,并在過濾器中列舉該函數可能抛出的異常。

例如:

int

Function(IN const char* pcName) throw(byExp, exception);

如果一個函數本身及其直接調用的函數不會顯式抛出異常(沒有指定異常過濾器),那麼該函數可以省略異過濾器。

特别地:如果一個函數内部顯式地捕獲了任何可能的異常(例如:使用了 "catch (...)" ),并且保證不抛出任何異常,那麼應該在其聲明和定義中顯式地指定一個空異常過濾器:"throw()"。

例如:

int

Function(IN const char* pcName) throw();

特别地:程序入口函數("main()")不應當使用異常過濾器。

除了空異常過濾器有時可幫助編譯器完成一些優化以外,異常過濾器的主要作用反倒是為程式員提供函數出錯時的行為描述。這些資訊對于函數的使用者來說十分有用。是以,即使僅作為一種文檔性質的措施,異常過濾器也應當被保留下來。

代碼段注釋

如果函數體中的代碼較長,應該根據功能不同将其分段。代碼段間以空行分離,并且每段代碼都以代碼段分割注釋作為開始。

例如:

void

CXXX::Function(IN void* pmodAddr)

{

    if (NULL == pmodAddr)

    {

        return;

    }

    { CSessionLock iLock(mxLock);

        // =====================================================================

        // = 判斷指定子產品是不是剛剛被裝入,由于在NT系列平台中,“A”系列函數都是

        // = 由“W”系列函數實作的。 是以可能會有一次LoadLibrary産生多次本函數調

        // = 用的情況。為了增加效率,特設此靜态變量判斷上次調用是否與本次相同。

        static PVOID pLastLoadedModule = NULL;

        if (pLastLoadedModule == pmodAddr)

        {

            return;  // 相同,忽略這次調用

        }

        pLastLoadedModule = pmodAddr;

        // =====================================================================

        // = 檢查這個子產品是否在旁路子產品表中

        stringEx stModName;

        if (!BaiY_IMP::GetModuleNameByAddress(pmodAddr, stModName))

        {

            return;

        }

        if (CHookProc::sm_sstByPassModTbl.find(stModName)

            != CHookProc::sm_sstByPassModTbl.end())

        {

            return;

        }

        // =====================================================================

        // = 在這個子產品中HOOK所有存在于HOOK函數表中的函數

        PROCTBL::iterator p = sm_iProcTbl.begin();

        for (; p!=sm_iProcTbl.end(); ++p)

        {

            p->HookOneModule(pmodAddr);

        }

    } // SessionLock

}

明顯地,如果需要反複用到一段代碼的話,這段代碼就應當 被抽取成一個函數。

當一個函數過長(超過100行)或代碼的意圖不明确時,為了便于閱讀和了解,也應當将其中的一些代碼段實作為單獨的函數。

特别地,對由于如加密及性能優化等特殊原因無法提取為一個單獨函數的代碼段,應當使用特别代碼段注釋顯式分割。當然,類似情況應當盡量使用内聯函數或編譯器提供的強制性内聯函數代替。

例如:

void

CXXX::Function(void)

{

    // ...

    // @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@

    // @@ 擷取首網卡的 MAC 位址

    typedef CTmpHandle<IP_ADAPTER_INFO, FreeDeletor<IP_ADAPTER_INFO> >

            THADAPTERINFO;

    byteEx btAddr;

    THADAPTERINFO thAdapterInfo;

    thAdapterInfo = (IP_ADAPTER_INFO*) malloc(sizeof(IP_ADAPTER_INFO));

    ULONG ulOutBufLen = sizeof(IP_ADAPTER_INFO);

    // Make an initial call to GetAdaptersInfo to get

    // the necessary size into the ulOutBufLen variable

    if (ERROR_SUCCESS != ::GetAdaptersInfo(thAdapterInfo, &ulOutBufLen)) 

    {

        thAdapterInfo = (IP_ADAPTER_INFO*) malloc(ulOutBufLen);

    }

    if (NO_ERROR != ::GetAdaptersInfo(thAdapterInfo, &ulOutBufLen))

    {

    #ifdef DEBUG

        CLog::DebugMsg(byT("lic verifier"), byT("failed verifying license"));

    #endif

        CProcess::Exit(-97);

    }

    btAddr.assign(thAdapterInfo->Address, thAdapterInfo->AddressLength);

    // @@ 擷取首網卡的 MAC 位址

    // @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@

    // ... }

調用系統API

所有系統API調用前都要加上全局名稱解析符 "::"。

例如:

::MessageBoxA(NULL, gcErrorMsg, "!FATAL ERROR!", MB_ICONSTOP|MB_OK);

if (0 == ::GetTempFileName(m_basedir.c_str(), byT("bai"), 0, stR.ref()))

{

    // ...

}

讓相同的代碼隻出現一次 為了使程式更容易調試、修改,盡量降低日後維護的複雜性,應該把需要在一個以上位置使用的代碼段封裝成函數。哪怕這段代碼很短,為了以後維護友善着想,也應當将其封裝為内聯函數。

關于函數的例子,請參見:函數的風格與版式例子

關于函數的模闆,請參見:函數模闆

變量、常量
C++編碼規範與指導

聲明格式 變量、常量的聲明格式如下:
[存儲類] 類型 變量名;
其中:
  • 以 "[ ]" 包覆的為可選項目。
  • "存儲類 " 的說明見下文
定義格式 變量、常量的定義格式如下:
[存儲類] 類型 變量名 = 初始值;
其中:
  • 以 "[ ]" 包覆的為可選項目。
  • "存儲類 " 的說明見下文
存儲類

除 "auto" 類型以外,諸如 "extern", "static", "register", "volatile" 等存儲類均不可省略,且必須在聲明和定義中一緻地使用(即:不允許僅在聲明或定義中使用)。

特别地,由于 "auto" 關鍵字在 C++11 中已被用作類型推斷,是以聲明局部變量時,"auto" 存儲類必須被省略。

成員變量的存儲類

由于C++語言的限制,成員變量的 "static" 等存儲類說明不允許出現在變量定義中。

但是為了明确起見,這些存儲類應以注釋的形式在定義中給出。

例如:

 int CThread::sm_nPID = 0;
指針或引用類型的定義和聲明 在聲明和定義多個指針或引用變量/常量時,每個變量至少占一行。例如:

int* pn1,

   * pn2 = NULL,

   * pn3;

char* pc1;

char* pc2;

char* pc3;

// 錯誤的寫法:

int* pn11, *pn12, *pn13;

常指針和指針常量

聲明/定義一個常指針(指向常量的指針)時,"const" 關鍵字一律放在類型說明的左側。

聲明/定義一個指針常量(指針本身不能改變)時,"const" 關鍵字一律放在變量左側、類型右側。

例如:

const char* pc1;       // 常指針

char* const pc2;       // 指針常量

const char* const pc3; // 常指針常量

// 錯誤的寫法:

char const* pc1;  // 與 const char* pc1 含義相同,但不允許這樣寫

全局變量、常量的注釋

全局變量、常量的注釋獨占一行,并用 "//!" 開頭。

例如:

//! 目前程序的ID 

static int sg_nPID = 0;

//! 分割符 

static const char* pcDTR = "\\/";

類型轉換 禁止使用C風格的 "(類型)" 類型轉換,應當優先使用C++的 "xxx_cast" 風格的類型轉換。C++風格的類型轉換可以提供豐富的含義和功能,以及更好的類型檢查機制,這對代碼的閱讀、修改、除錯和移植有很大的幫助。其中:
static_cast static_cast 用于編譯器認可的,安全的靜态轉換,比如将 "char" 轉為 "int" 等等。該操作通常在編譯時完成,但有可能調用使用者定義的類型轉換操作或非 explicit 的單參(或至少從第二個參數開始帶預設值的)構造函數 。
reinterpret_cast reinterpret_cast 用于編譯器不認可的,不安全的靜态轉換,比如将 "int*" 轉為 "int" 等等。這種轉換有可能産生可移植性方面的問題,該操作在編譯時完成。(注意:reinterpret_cast 比 C 風格的類型轉換還要野蠻,它不進行任何位址對齊和調整,也不調用任何使用者定義的類型轉換操作)
const_cast const_cast 用于将一個常量轉化為相應類型的變量,比如将 "const char*" 轉換成 "char*" 等等。這種轉換可能伴随潛在的錯誤。該操作在編譯時完成 。
dynamic_cast dynamic_cast 是 C++ RTTI 機制的重要展現,用于在類層次結構中漫遊。dynamic_cast 可以對指針和引用進行自由度很高的向上、向下和交叉轉換。被正确使用的 dynamic_cast 操作将在運作時完成。反之,若編輯器關閉了 RTTI 支援,或被轉換的類層次結構中沒有抽象類存在,則此操作在編譯時完成(有些編譯器會給出警告)。

此外,對于定義了單參構造函數或類型轉換操作的類來說,應當優先使用構造函數風格的類型轉換,如:'string("test")' 等等。

通常來說,"xxx_cast" 格式的轉換與構造函數風格的類型轉換之間最大的差別在于:構造函數風格的轉換經常會生成新的臨時對象,可能伴随相當的時間和空間開銷。而 "xxx_cast" 格式的轉換隻是告訴編譯器,将指定記憶體中的資料當作另一種類型的資料看待,這些操作一般在編譯時完成,不會對程式的運作産生額外開銷。當然,"dynamic_cast" 和某些 "static_cast" 則例外。

參見:RTTI、虛函數和虛基類的開銷分析和使用指導

枚舉、聯合、typedef
C++編碼規範與指導

枚舉、聯合的定義格式 枚舉、聯合的定義格式為:

//! 說明(可選)

enum|union 名稱

{

    内容  // 注釋(可選)

};

例如:

//! 服務的狀态 

enum SRVSTATE

{

    SRV_INVALID  = 0,  // 無效(尚未啟動)

    SRV_STARTING = 1,

    SRV_STARTED,

    SRV_PAUSING,

    SRV_PAUSED,

    SRV_STOPPING,

    SRV_STOPPED

};

//! 32位整數

union INT32

{

    unsigned char  cByte[4];

    unsigned short nShort[2];

    unsigned long  nFull;

};

typedef的定義格式 typedef 的定義格式為:

//! 說明(可選)

typedef 原類型 類型别名;

例如:

//! 傳回值類型

typedef int EXITCODE;

//! 字元串數組類型

typedef vector<string> VSTR;

C++編碼規範與指導

宏是C/C++編譯環境提供給使用者的,在編譯開始前(編譯預處理階段)執行的唯一可程式設計邏輯。
何時使用宏 應當盡量減少宏的使用,在所有可能的地方都使用常量、模版和内聯函數來代替宏。
邊界效應 使用宏的時候應當注意邊界效應,例如,以下代碼将會得出錯誤的結果:

#define PLUS(x,y) x+y

cout << PLUS(1,1) * 2;

以上程式的執行結果将會是 "3",而不是 "4",因為 "PLUS(1,1) * 2" 表達式将會被展開為:"1 + 1 * 2"。

是以在定義宏的時候,隻要允許,就應該為它的替換内容括上 "( )" 或 "{ }"。例如:

#define PLUS(x,y) (x+y)

#define SAFEDELETE(x) {delete x; x=0}

對複雜的宏實行縮進

有時為了實作諸如:對編譯器和目标平台自适應;根據使用者選項編譯不同子產品等機制,需要使用大量較為複雜的宏定義塊。在宏比較複雜(代碼塊多于5行)的地方,為了便于閱讀和了解,應當遵循與普通C++代碼相同的原則進行縮進和排版。

為了差別于其他語句和便于閱讀,宏語句的 "#" 字首不要與語句本身一起縮進,例如:

//! Windows

#if defined(__WIN32__)

#   if defined(__VC__) || defined(__BC__) || defined(__GNUC__) // ...

#       define BAIY_EXPORT __declspec(dllexport)

#       define BAIY_IMPORT __declspec(dllimport)

#   else // 編譯器不支援 __declspec()

#       define BAIY_EXPORT

#       define BAIY_IMPORT

#   endif

//! OS/2

#elif defined(__OS2__)

#   if defined (__WATCOMC__)

#       define BAIY_EXPORT __declspec(dllexport)

#       define BAIY_IMPORT

#   elif !(defined(__VISAGECPP__) && (__IBMCPP__<400 || __IBMC__<400))

#       define BAIY_EXPORT _Export

#       define BAIY_IMPORT _Export

#   endif

//! Macintosh

#elif defined(__MAC__)

#   ifdef __MWERKS__

#       define BAIY_EXPORT __declspec(export)

#       define BAIY_IMPORT __declspec(import)

#   endif

// Others

#else

#   define BAIY_EXPORT

#   define BAIY_IMPORT

#endif

名空間
C++編碼規範與指導

名空間的使用 名空間可以避免名字沖突、分組不同的接口以及簡化命名規則。應當盡可能地将所有接口都放入适當的名字空間中。
将實作和界面分離

提供給使用者的界面和用于實作的細節應當分别放入不同的名空間中。

例如:如果将一個軟體子產品的所有接口都放在名空間 "MODULE" 中,那麼這個子產品的所有實作細節就可以放入名空間 "MODULE_IMP" 中,或者 "MODULE" 内的 "IMP" 中。

異常
C++編碼規範與指導

異常使C++的錯誤處理更為結構化;錯誤傳遞和故障恢複更為安全簡便;也使錯誤處理代碼和其它代碼間有效的分離開來。
何時使用異常

異常機制隻用在發生錯誤的時候,僅在發生錯誤時才應當抛出異常。這樣做有助于錯誤處理和程式動作兩者間的分離,增強程式的結構化,還保證了程式的執行效率。

确定某一狀況是否算作錯誤有時會很困難。比如:未搜尋到某個字元串、等待一個信号量逾時等等狀态,在某些情況下可能并不算作一個錯誤,而在另一些情況下可能就是一個緻命錯誤。

有鑒于此,僅當某狀況必為一個錯誤時(比如:配置設定存儲失敗、建立信号量失敗等),才應該抛出一個異常。而對另外一些模棱兩可的情況,就應當使用傳回值等其它手段報告 。

此外,在發生錯誤的位置,已經能夠獲得足夠的資訊處理該錯誤的情況不屬于異常,應當對其就地處理。隻有無法獲得足夠的資訊來處理發生的錯誤時,才應該抛出一個異常。

用異常代替goto等其它錯誤處理手段 曾經被廣泛使用的傳統錯誤處理手段有goto風格和do...while風格等,以下是一個goto風格的例子:
C++編碼規範與指導

//! 使用goto進行錯誤處理的例子

bool

Function(void)

{

    int nCode, i;

    bool r = false;

    // ...

    if (!Operation1(nCode))

    {

        goto onerr;

    }

    try 

    {

        Operation2(i);

    }

    catch (...)

    {

        r = true;

        goto onerr;

    }

    r = true;

onerr:

    // ... 清理代碼

    return r;

}

由上例可見,goto風格的錯誤處理至少存在問題如下:
  • 錯誤處理代碼和其它代碼混雜在一起,使程式不夠清晰易讀 。
  • 函數内的變量必須在第一個 "goto" 語句之前聲明,違反就近原則。
  • 多處跳轉的使用破壞程式的結構化,影響程式的可讀性,使程式容易出錯 。
  • 對每個會抛出異常的操作都需要用額外的 try...catch 塊檢測和處理。
  • 稍微複雜一點的分類錯誤處理要使用多個标号和不同的goto跳轉(如: "goto onOp1Err", "goto onOp2Err" ...)。這将使程式變得無法了解和錯誤百出。
再來看看 do...while 風格的錯誤處理:
C++編碼規範與指導

//! 使用do...while進行錯誤處理的例子

bool

Function(void)

{

    int nCode, i;

    bool r = false;

    // ...

    do

    {

        if (!Operation1(nCode))

        {

            break;

        }

        do 

        {

            try 

            {

                Operation2(i);

            }

            catch (...)

            {

                r = true;

                break;

            }

        } while (Operation3())

        r = true;

    } while (false);

    // ... 清理代碼

    return r;

}

與 goto 風格的錯誤處理相似,do...while 風格的錯誤處理有以下問題:
  • 錯誤處理代碼和其它代碼嚴重混雜,使程式非常難以了解 。比如上例中的外層循環用于錯誤處理,而内層的 do...while 則是正常的業務邏輯。
  • 需要進行分類錯誤處理時非常困難,通常需要事先設定一個标志變量,并在清理時使用 "switch case" 語句進行分檢。
  • 對每個會抛出異常的操作都需要用額外的 try...catch 塊檢測和處理 。
此外,還有一種更糟糕的錯誤處理風格——直接在出錯位置就地完成錯誤處理:
C++編碼規範與指導

//! 直接進行錯誤處理的例子

bool

Function(void)

{

    int nCode, i;

    // ...

    if (!Operation1(nCode))

    {

        // ... 清理代碼

        return false;

    }

    try 

    {

        Operation2(i);

    }

    catch (...)

    {

        // ... 清理代碼

        return true;

    }

    // ...

    // ... 清理代碼

    return true;

}

這種錯誤處理方式所帶來的隐患可以說是無窮無盡,這裡不再列舉。

與傳統的錯誤處理方法不同,C++的異常機制很好地解決了以上問題。使用異常完成出錯處理時,可以将大部分動作都包含在一個try塊中,并以不同的catch塊捕獲和處理不同的錯誤:

//! 使用異常進行錯誤處理的例子

bool

Function(void)

{

    int nCode, i;

    bool r = false;

    try 

    {

        if (!Operation1(nCode))

        {

            throw false;

        }

        Operation2(i);

    }

    catch (bool err)

    {

        // ...

        r = err;

    }

    catch (const excption& err)

    {

        // ... excption類錯誤處理

    }

    catch (...)

    {

        // ... 處理其它錯誤

    }

    // ... 清理代碼

    return r;

}

以上代碼示例中,錯誤處理和動作代碼完全分離,錯誤分類清晰明了,好處不言而喻。
構造函數中的異常

在構造函數中抛出異常将中止對象的構造,這将産生一個沒有被完整構造的對象。

對于C++來說,這種不完整的對象将被視為尚未完成建立動作而不被認可,也意味着其析構函數永遠不會被調用。這個行為本身無可非議,就好像警察局不會為一個被流産的嬰兒發戶口然後再開個死亡證明書一樣。但有時也會産生一些問題,例如:

class CSample

{

    // ...

    char* m_pc;

};

CSample::CSample()

{

    m_pc = new char[256];

    // ...

    throw -1;  // m_pc将永遠不會被釋放

}

CSample::~CSample()  // 析構函數不會被調用

{

    delete[] m_pc;

}

解決這個問題的方法是在抛出異常以前釋放任何已被申請的資源。一種更好的方法是使用一個滿足“資源申請即初始化(RAII)”準則的類型(如:句柄類、靈巧指針類等等)來代替一般的資源申請與釋放方式,如:

templete <class T>

struct CAutoArray

{

    CAutoArray(T* p = NULL) : m_p(p) {};

    ~CAutoArray() {delete[] m_p;}

    T* operator=(IN T* rhs) 

    {

        if (rhs == m_p)

            return m_p;

        delete[] m_p;

        m_p = rhs;

        return m_p;

    }

    // ...

    T* m_p;

};

class CSample

{

    // ...

    CAutoArray<char> m_hc;

};

CSample::CSample()

{

    m_hc = new char[256];

    // ...

    throw -1;  // 由于m_hc已經成功構造,m_hc.~CAutoPtr()将會

               // 被調用,是以申請的記憶體将被釋放

}

注意:上述CAutoArray類僅用于示範,對于所有權語義的通用自動指針,應該使用C++标準庫中的 "auto_ptr" 模闆類。對于支援引用計數和自定義銷毀政策的通用句柄類,可以使用白楊工具庫中的 "CHandle" 模闆類。
析構函數中的異常 析構函數中的異常可能在2種情況下被抛出:
  1. 對象被正常析構時。
  2. 在一個異常被抛出後的退棧過程中——異常處理機制退出一個作用域,其中所有對象的析構函數都将被調用。
由于C++不支援異常的異常,上述第二種情況将導緻一個緻命錯誤,并使程式中止執行。例如:

class CSample

{

    ~CSample();

    // ...

};

CSample::~CSample()

{

    // ...

    throw -1;  // 在 "throw false" 的過程中再次抛出異常

}

void

Function(void)

{

    CSample iTest;

    throw false;  // 錯誤,iTest.~CSample()中也會抛出異常

}

如果必須要在析構函數中抛出異常,則應該在異常抛出前用 "std::uncaught_exception()" 事先判斷目前是否存在已被抛出但尚未捕獲的異常。例如:

// uncaught_exception() 函數在這個頭檔案中聲明

#include <exception>

class CSample

{

    ~CSample();

    // ...

};

CSample::~CSample()

{

    // ...

    if (!std::uncaught_exception()) // 沒有尚未捕獲的異常

    {

        throw -1;  // 抛出異常

    }

}

void

Function(void)

{

    CSample iTest;

    throw false;  // 可以,iTest.~CSample()不會抛出異常

}

new  時的異常 C++ 标準(ISO/IEC 14882:2003)第 15.2 節中明确規定,在使用 new 或 new[] 操作建立對象時,如對象的構造函數抛出了異常,則該對象的所有成員和基類都将被正确析構,如果存在一個與使用的 operator new 嚴格比對的 operator delete,則為這個對象所配置設定的記憶體也會被釋放。例如:

class CSample

{

    CSample() { throw -1; }

    static void* operator new(IN size_t n)

        { return malloc(n); }

    static void operator delete(IN void* p)

        { free(p); }

    static void* operator new(IN size_t n, IN CMemMgr& X)

        { return X.Alloc(n); } // 缺少比對的 operator delete

};

void

Function(void)

{

    CSample* p1 = new CSample; // 有比對的 operator delete,為 p1 配置設定的記憶體會被釋放

    CSample* p2 = new(iMyMemMgr) CSample; // 沒有比對的 operator delete,記憶體洩漏!為 p2 配置設定的記憶體永遠不會被釋放

}

// 編譯器實際生成的代碼像這樣:

void

Function(void)

{

    CSample* p1 = CSample::operator new(sizeof(CSample)); 

    try { p1->CSample(); } catch(...) {CSample::opertaor delete(p1); throw; }

    CSample* p2 = CSample::operator new(sizeof(CSample), iMyMemMgr);

    p2->CSample();

}

這裡順便提一句,delete 操作隻會比對普通的 operator delete(即:全局或類中的 operator delete(void*) 和類中的 operator delete(void*, size_t)),如果像上例中的 p2 那樣使用了一個高度自定義的 operator new,使用者就需要自己完成析構和釋放記憶體的動作,例如:

    // ...

    p2->~CSample();

    CSample::operator delete(p2, iMymemMgr);

delete 時的異常

C++ 标準中明确規定,如果在一個析構函數中中途傳回(不管通過 return 還是 throw),該析構函數不會立即傳回,而是會逐一調用所有成員和基類的析構函數後才會傳回。但是标準中并沒有說明如果這個異常是在 delete 時發生的(即:該對象是由 new 建立的),此對象本身所占用的堆存儲是否會被釋放(即:在 delete 時析構函數抛出異常會不會調用 operator delete 釋放這個對象占用的記憶體)。

在實際情況中,被 delete 的對象析構函數抛出異常後,GCC、VC 等流行的 C++ 編譯器都不會自動調用 operator delete 釋放對象占用的記憶體。這種與 new 操作不一緻的行為,其背後的理念是:在構造時抛出異常的對象尚未成功建立,系統應當收回事先為其配置設定的資源;而析構時抛出異常的對象并未成功銷毀,系統不能自動回收它使用的記憶體(意即:系統僅自動回收确定完全無用的資源)。

例如:如果一個對象在構造時申請了系統資源(比如:打開了一個裝置)并保留了 相應的句柄,但在析構時歸還該資源失敗(例如:關閉裝置失敗),則自動調用 operator delete 會丢失這個尚未關閉的句柄,導緻使用者永遠失去向系統歸還資源或者執行進一步錯誤處理的機會。反之,如果這個對象在構造時就沒能成功地申請到相應資源,則自動回收預配置設定給它的記憶體空間是安全的,不會産生任何資源洩漏。

但是應當注意到,如果一個對象在析構時抛出了異常,則這個對象很可能已經處于一個不完整 、不一緻的狀态。此時通路該對象中的任何非靜态成員都是不安全的。是以,應當在被抛出的異常中包含完成進一步處理的足夠資訊 (比如:關閉失敗的句柄)。這樣捕獲到這個異常的使用者就可以安全地釋放該對象占用的記憶體, 僅依靠異常對象完成後續處理。例如:

//! delete 時異常處理的例子

void

Function(void)

{

    CSample* p1 = new CSample;

    // ...

    try 

    {

        delete p1;

    }

    catch (const sampleExp& err)

    {

        CSample::operator delete(p1); // 釋放 p1 所占用的記憶體 

        // 使用 err 對象完成後續的錯誤處理... 

    }

}

異常的組織

異常類應該以繼承的方式組織成一個層次結構,這将使以不同粒度分類處理錯誤成為可能。

通常,某個軟體生産組織的所有異常都從一個公共的基類派生出來。而每個類的異常則從該類所屬子產品的公共異常基類中派生。例如:

C++編碼規範與指導
異常捕獲和重新抛出
  • 異常捕獲器的書寫順序應當由特殊到一般(先子類後基類),最後才是處理所有異常的捕獲器("catch(...)")。否則将使某些異常捕獲器永遠不會被執行。
  • 為避免捕獲到的異常被截斷,異常捕獲器中的參數類型應當為常引用型或指針型。
  • 在某級異常捕獲器中無法被徹底處理的錯誤可以被重新抛出。重新抛出采用一個不帶運算對象的 "throw" 語句。重新抛出的對象就是剛剛被抛出的那個異常,而不是處理器捕獲到的(有可能被截斷的)異常。
例如:

try 

{

    // ...

}

// 公鑰加密錯誤

catch (const CPubKeyCipher::Exp& err)  

{

    if (可以恢複)

    {

        // 恢複錯誤

    }

    else

    {

        // 完成能做到的事情

        throw;  // 重新抛出

    }    

}

// 處理其它加密庫錯誤

catch (const CryptoExp& err)

{

    // ...

}

// 處理其它本公司子產品抛出的錯誤

catch (const CompanyExp& err)

{

    // ...

}

// 處理 dynamic_cast 錯誤

catch (const bad_cast& err)

{

    // ...

}

// 處理其它标準庫錯誤

catch (const exception& err)

{

    // ...

}

// 處理所有其它錯誤

catch (...)

{

    // 完成清理和日志等基本處理...

    throw;  // 重新抛出

}

異常和效率

對于絕大部分現代編譯器來說,在不抛出異常的情況下,異常處理的實作在運作時幾乎不會有任何額外開銷。相反,很多時候,異常機制比傳統的通過傳回值判斷錯誤的開銷還來得稍微小些。

相對于函數傳回和調用的開銷來講,異常抛出和捕獲的開銷通常會大一些。不過錯誤處理代碼通常不會頻繁調用,再說傳統的錯誤處理方式也不是沒有代價的。是以錯誤處理時開銷稍大一點基本上不是什麼問題。這也是我們提倡僅将異常用于錯誤處理的原因之一。

更多關于實作細節和效率的讨論,參見:C++異常機制的實作方式和開銷分析 和 RTTI、虛函數和虛基類的開銷分析和使用指導 等小節。

修改标記
C++編碼規範與指導

在代碼交叉審查,或使用帶完整源代碼的第三方庫時,經常需要為某些目的修改源碼。這時應當為被改動的部分添加修改标記。
何時使用修改标記

修改标記通常僅用于修改者不是被修改子產品(或項目)的主要作者時,但也可以用于在調試、重構或添加新特性時進行臨時标注。

在交叉審查中使用的修改标記,當原作者已經确認并将其合入主要版本之後,應當予以消除,以避免由于多次交叉審查累積的标記混亂。但是相應的修改應當記入檔案頭的修改記錄中。

修改标記的格式 修改标記分為單行标記和段落标記兩種,單行标記用于訓示對零星的單行代碼進行的修改,段落标記則用于指出對一組任意長度的代碼作出的修改。它們的格式如下:

// 單行标記:

// code ...; // by <修改者> - <目的> [@ YYYY-MM-DD(可選的修改日期)]

// 段落标記:

// [[ by <修改者> - <目的> [@ YYYY-MM-DD(可選的修改日期)]

//    詳細說明(可選,可多行)

// ... // 被修改的代碼段落

// ]] [by <修改者>]

注意段落标記結尾的 "by <修改者>" 字段是可選的。

此外,在比較混亂或較長的代碼段中,可以将段落開始("// [[")和段落結束("// ]]")标記擴充層次結構更為明顯的:"// ---- [[" 和 "// ---- ]]"

例如:

    // [[ by BaiYang - limit @ 2005-03-29

    //    add pre compile and delay binding support to "limit [s,]n". 

    void setStatementLimit(dbQuery const& q) {

        // ...

    }

    // ]]

// ...

// ---- [[ by Mark - multithread

void dbCompiler::compileLimitPart(dbQuery& query)

    // ...

    int4* lp1 = INVPTR; // by BaiYang - limit

    switch (scan()) 

    {

      case tkn_iconst:

    // ...

}

// ---- ]] by Mark

修改标記的語言

修改标記當中的說明性文字應當盡量選擇與被修改項目一緻的語言書寫。例如在全英文的項目中應當盡量避免添加中文注釋。

否則能完全看懂修改後項目的程式員将會被限制于同時掌握多種自然語言的人。

C++編碼規範與指導

版本控制

  • 源代碼的版本按檔案的粒度進行維護。
  • 建立一個新檔案時,其初始版本為 "1.0",建立過程中的任何修改都不需要增加修改記錄。
  • 從軟體第一次正式釋出開始,對其源檔案的每次修改都應該在檔案頭中加入相應的修改記錄,并将檔案的子版本加1。
  • 更新軟體的主版本時,其源檔案的相應主版本号随之增加。與建立新檔案時一樣,在該主版本第一 次釋出之前,對檔案的任何修改都不需要再增加修改記錄。
C++編碼規範與指導

英文版

對于為海外使用者編寫的代碼,所有注釋都統一使用英文。關于各标準注釋的英文模闆,請參考:常用英文注釋一覽
C++編碼規範與指導

自動工具與文檔生成

縱觀 MSDN、unix/linux manaul(man)、wxWindows Doc等享有盛譽的開發文檔都是手工或半手工編寫的。相反,那些完全由自動工具生成的文檔基本上都是被廣大程式員唾棄的 
C++編碼規範與指導

由此可以看出,以現今的人工智能科技,完全由機器生成的文檔,仍然無法滿足人類閱讀的需要。但是一份注釋詳實、版式規範的源代碼配合一些簡單的工具确實可以大大降低文檔編寫的工作量。從這樣的源碼中抽取出來的資訊,通常隻要稍加整理和修改就可以得到一份媲美MSDN的文檔了。

詳情參見:軟體子產品使用者文檔模闆

C++編碼規範與指導

關于本規範的貫徹實施

像這樣一套完整、詳細、繁瑣又重要的規範,最難的恐怕就是貫徹實施的環節了。有鑒于此,特提供幾點意見:
設立代碼審查小組 對于大型開發團隊,可以設立專門的代碼審查小組,對項目的所有源碼進行有計劃的審查和評估。
設立交叉審查制度 在程式員間設立源碼的交叉審查制度,鼓勵大家互相督促。
提供專門的檢查工具 以IDE插件和獨立應用程式的形式提供專門的自動化批量代碼檢查工具,提高審查與自我檢查的效率和精度。
設立獎懲和激勵機制 不用多說了,金錢加大棒。堅決執行的公開表揚、發獎金;發證書,頑冥不化的K一頓然後廢了他 
C++編碼規範與指導
C++編碼規範與指導

術語表

術語 解釋
API 應用程式程式設計接口
UI 使用者界面
GUI 圖形使用者界面
OOP 面向對象的程式開發
IDE 內建開發環境
C++編碼規範與指導
沒有規矩 否成方圓 
C++編碼規範與指導
C++編碼規範與指導

參考文獻

名稱 作者 釋出/出版日期
C++程式設計語言——特别版 Bjarne Stroustrup 2002
ANSI/ISO C++ Professional Programmer's Handbook Denny Kalev 1999
ISO/IEC 14882 Programming Languages -- C++ the ANSI C++ community 1998
Microsoft MSDN 微軟公司 期刊,參照版本為2004年4月
linux/unix線上手冊(man) - -
wxWindows 2.4.2 Doc wxWindows September 2003
高品質C++/C程式設計指南 林銳 博士 2001年7月24日
人月神話——20周年紀念版 Frederick P. Brooks Jr. 2002
C/C++程式設計規範(華為) 蘇信南 1997-5-5
C++編碼規範(中興) - -
前台軟體程式設計細則(中興) - -
軟體評審 PMT Community 2002
UML 使用者指南

Grady Booch

James Rumbaugh

Ivar Jacobson

2001年6月
重構——改善現有代碼的設計 Martin Fowler 2003年8月
設計模式 Erich Gamma 等 2000年9月
C++編碼規範與指導

C++成長篇

本篇歸納了一C++程式員成長中的各個階段,以及踏入該階段的最佳武林秘笈。本篇僅供各位大俠茶餘飯後時拍磚用 
C++編碼規範與指導

這裡隻圍繞純粹的C++程式設計語言進行讨論。當然,要成為一個稱職的程式員,計算機原理、作業系統、資料庫等其它方面的專業知識也是十分重要的。

秘笈 作者
初入江湖——慘不忍睹

C++程式設計教程

 - 或 -

C++語言程式設計(第二版)

錢能

鄭莉 董淵

小有名氣——将就着用
Thinking in C++ 2nd edition Bruce Eckel
名動一方——在大是大非的問題上立場堅定
Effective C++(第二版) 和 More Effective C++ Scott Meyers (Lostmouse、候捷 等 譯)
天下聞名——正确的使用C++的每個特性

C++程式設計語言——特别版

 - 和 -

ANSI/ISO C++ Professional Programmer's Handbook

Bjarne Stroustrup (裘宗燕 譯)

Denny Kalev

一代宗師——掌握通用程式設計思想
範型程式設計與STL Matthew H. Austem (候捷 譯)
超凡入聖——清楚C++的每個細節
ISO/IEC 14882: Programming Languages-C++ ISO/IEC
天外飛仙——透過C++的軍大衣,看到赤裸裸的彙編碼
GCC的源碼爛熟于胸,有事沒事的随便寫個編譯器玩玩~
C++編碼規範與指導

轉自: http://baiy.cn/doc/cpp/

繼續閱讀