1. 程式人生 > >Google的C++程式碼規範

Google的C++程式碼規範

        Google C++ 編碼規範很早就已經公開了,李開復也在其微博上公開分享:”我認為這是地球上最好的一份C++程式設計規範,沒有之一,建議廣大國內外IT研究使用。“

        Google C++ Style Guide是一份不錯的C++編碼指南,下面是一張比較全面的說明圖,可以在短時間內快速掌握規範的重點內容。不過規範畢竟是人定的,記得活學活用。

  1. 保持一致也非常重要,如果你在一個檔案中新加的程式碼和原有程式碼風格相去甚遠的話,這就破壞了檔案本身的整體美觀也影響閱讀,所以要儘量避免。
  2. 一些條目往往有例外,比如下面這些,所以本圖不能代替文件,有時間還是把PDF認真閱讀一遍吧。

        異常在測試框架中確實很好用

        RTTI在某些單元測試中非常有用

        在記錄日誌時可以使用流     操作符過載 不提倡使用,有些STL 演算法確實需要過載operator==時可以這麼做。

注:原圖較大,在新標籤頁中開啟或儲存到本地開啟更清晰

標頭檔案

  函式引數順序

  C/C++函式引數分為輸入引數和輸出引數兩種,有時輸入引數也會輸出(注:值被修改時)。輸入引數一般傳值或常數引用(const references),輸出引數戒輸入/輸出引數為非常數指標(non-const pointers)。對引數排序時,將所有輸入引數置於輸出引數之前。不要僅僅因為是新新增的引數,就將其置於最後,而應該依然置於輸出引數之前。這一點並不是必須遵循的規則,輸入/輸出兩用引數(通常是類/結構體變數)混在其中,會使得規則難以遵守。

  個人感受:這條規則相當重要,自己寫程式碼的時候可能沒有太大感覺,但是在閱讀別人程式碼的時候感覺特別明顯。如果程式碼按照這種規範來寫,從某種角度來說,這段程式碼具有“自注釋”的功能,那麼在看程式碼的時候就會比較輕鬆。Doom3的程式碼規範中提到,“Use ‘const’ as much as possible”,也是同樣的意義。當然,const除了閱讀方便以外,還有個很重要的就是防止編碼錯誤,一旦在程式中修改const變數,編譯器就會報錯,這樣就減少了人工出錯了可能性,這點尤為重要!

  包含檔案的名稱及次序

  將包含次序標準化可增強可讀性、避免隱藏依賴(hidden dependencies,注:隱藏依賴主要是指包含的檔案編譯),次序如下:C 庫、C++庫、其他庫的.h、專案內的.h。

  專案內標頭檔案應挄照專案原始碼目錄樹結構排列,並且避免使用UNIX檔案路徑.(當前目錄)和..(父目錄)。 

  舉例來說,google-awesome-project/src/foo/internal/fooserver.cc 的包含次序如下:    #include "foo/public/fooserver.h" // 優先位置       #include <sys/types.h>    #include <unistd.h>    #include <hash_map>    #include <vector>    #include "base/basictypes.h"    #include "base/commandlineflags.h"    #include "foo/public/bar.h"

  注意,對應的標頭檔案一定要先包含,這樣避免隱藏依賴,隱藏依賴的問題不懂的可以去Google,網上有很多資料。另外,《C++程式設計思想》中提到的包含次序正好相反,從特殊到一般,但是有一點和Google程式碼規範是一樣的,那就是對應的標頭檔案是第一個包含。對於隱藏依賴的問題,以前只是習慣性的把對應的標頭檔案放第一個,沒有想過為什麼,現在學習了……

作用域

  全域性變數

  class 型別的全域性變數是被禁止的,內建型別的全域性變數是允許的,當然多執行緒程式碼中非常數全域性變數也是被禁止的。永遠不要使用函式返回值初始化全域性變數。

  不幸的是,全域性變數的建構函式、解構函式以及初始化操作的呼叫順序只是被部分規定,每次生成有可能會有變化,從而導致難以發現bug。因此,禁止使用class型別的全域性變數(包括STL的string,vector等),因為它們的初始化順序可能會導致出現問題。內建型別和由內建型別構成的沒有建構函式的結構體可以使用,如果你一定要使用class型別的全域性變數,請使用單件模式。

C++類

  建構函式的職責

  建構函式中只進行那些沒有實際意義的初始化,可能的話,使用Init()方法集中初始化為有意義(non-trivial)的資料。

  個人感受:這種做法可以從一開始就避免一些bug的出現,或更容易解決一些bug。建構函式+Init()函式初始化的方式與只用建構函式的方法相比,對計算機來說他們是沒有區別的,但是人是會犯錯的,這一條程式碼規範在某種程度上避免了一些人為錯誤,這個在開發中特別重要。

  拷貝建構函式

  僅在程式碼中需要拷貝一個類的物件的時候使用拷貝建構函式,不需要拷貝時使用DISALLOW_COPY_AND_ASSIGN這個巨集(關於這個巨集的內容,可以在網上搜到,我這裡就不寫了)。C++中物件的隱式拷貝是導致很多效能問題和bugs的根源。拷貝建構函式降低了程式碼可讀性,相比按引用傳遞,跟蹤按值傳遞的物件更加困難,物件修改的地方變得難以捉摸。

  個人感受:和上一項的目的類似,為了避免人為錯誤!拷貝建構函式本來是為了方便程式設計師程式設計了,但是卻有可能成為一個坑,為了避免這類問題,不需要拷貝時使用DISALLOW_COPY_AND_ASSIGN,這樣在需要呼叫拷貝建構函式的時候就會報錯,減少了人為出錯的可能性。C#和Java在這方面就做得比較好,雖然效能上不如C++,但是人為出錯的概率減少了很多。當然,使用一定的程式碼規範,可以在一定程度上減少C++的坑。

  繼承

  雖然C++的繼承很好用,但是在實際開發中,儘量多用組合少用繼承,不懂的去看GoF的《Design Patterns》。

  但重定義派生的虛擬函式時,在派生類中明確宣告其為virtual。這一條是為了為了閱讀方便,雖然從語法的角度來說,在基類中聲明瞭virtual,子類可以不用再宣告該函式為virtual,但這樣一來閱讀程式碼的人需要檢索類的所有祖先以確定該函式是否為虛擬函式o(╯□╰)o。

  多重繼承

  雖然允許,但是隻能一個基類有實現,其他基類是介面,這樣一來和JAVA一樣了。這些東西在C#和JAVA中都進行了改進,直接從語法上解決問題。C++的靈活性過高,也是個麻煩的問題,只能通過程式碼規範填坑。

  介面

  虛基類必須以Interface為字尾,方便閱讀。閱讀方便。

  過載操作符

  除少數特定情況外,不要過載操作符!!!“==”和“=”的操作Euqals和CopyFrom函式代替,這樣更直觀,也不容易出錯。

  個人感受:看到這一條,我有點驚訝,在學習C++的時候,說過載操作符有神馬神馬好處,為什麼現在又說不要過載操作符呢?仔細看了他的文件,確實說的有道理,導致可能出現的bug見其具體文件。在實際應用中,由於C++的坑實在太多了,不得不把這種“好用”的東西幹掉,因為出了bug又找不到,是一件很O疼的事情。

  宣告次序

  1)typedefs和enums;

  2)常量;

  3)建構函式;

  4)解構函式;

  5)成員函式,含靜態成員函式;

  6)資料成員,含靜態資料成員。

  巨集 DISALLOW_COPY_AND_ASSIGN 置於private:塊之後,作為類的最後部分。

其他C++特性

  引用引數

  函式形參表中,所有的引用必須的const!

  個人感受:這麼做是為了防止引用引起的誤解,因為引用在語法上是值,卻有指標的意義。雖然引用比較好用,但是犧牲其某些方面的特性,換來軟體管理方面的便利,還是很值得了。

  預設引數

  禁止使用函式預設引數!

  個人感受:看到這一點的時候覺得有點因噎廢食了,其實預設引數感覺還是蠻好用的。當然從另外一個角度來說,要使用C++就不要怕這種小麻煩,如果因為使用這些特性造成了找不到的bug,那會損失更多時間。

  異常

  不要使用C++異常。

  這一點我沒有看懂,也許是因為它的異常機制沒有C#和Java那麼完善吧……畢竟在C#和Java裡面異常還是很好用的東東。

  

除了記錄日誌,不要使用流,使用printf之類的代替。

  這一條其實是有一些爭議的,當然大多數人認為程式碼一致性比較重要,所以選擇printf,具體的可以看原文文件。

  const的使用

  在任何可以的情況下都要使用const。

  這條規則贊一個,Doom3的程式碼規範裡也提到了這一條。這麼做有兩個好處,一個是防止程式出錯,因為修改了const型別的變數會報錯;另一個就是方便閱讀,使程式碼“自注釋”。雖然這麼做也有壞處,當然,總體來說利大於弊。

命名約定

  1、總體規則:不要隨意縮寫,如果說 ChangeLocalValue 寫作ChgLocVal還有情可原的話,把ModifyPlayerName寫作MdfPlyNm就太過分了,除函式名可適當為動詞外,其他命名儘量使用清晰易懂的名詞; 

  2、巨集、列舉等使用全部大寫+下劃線; 

  3、變數(含類、結構體成員變數)、檔案、名稱空間、存取函式等使用全部小寫+下劃線,類成員變數以下劃線結尾,全域性變數以g_開頭; 

  4、普通函式、型別(含類與結構體、列舉型別)、常量等使用大小寫混合,不含下劃線; 

  使用這套命名約定,可以使程式碼具有一定程度的“自注釋”功能,方便他人閱讀,也方便自己以後修改。當然3、4兩點也可以使用其他的命名約定,只要團隊統一即可。

格式 

  1、行寬原則上不超過80列,把22寸的顯示屏都佔完,怎麼也說不過去;

  2、儘量不使用非ASCII字元,如果使用的話,參考 UTF-8 格式(尤其是 UNIX/Linux 下,Windows 下可以考慮寬字元),儘量不將字串常量耦合到程式碼中,比如獨立出資原始檔,返不僅僅是風格問題了;

  3、UNIX/Linux下無條件使用空格,MSVC的話使用 Tab 也無可厚非; (我沒用過Linux,不懂為什麼在Linux下無條件使用空格)

  4、函式引數、邏輯條件、初始化列表:要麼所有引數和函式名放在同一行,要麼所有引數並排分行;

  5、除函式定義的左大括號可以置於行首外,包括函式/類/結極體/列舉宣告、各種語句的左大括號置於行尾,所有右大括號獨立成行;

  6、./->操作符前後丌留空格,*/&不要前後都留,一個就可,靠左靠右依各人喜好;

  7、預處理指令/名稱空間不使用額外縮排,類/結構體/列舉/函式/語句使用縮排;

  8、初始化用=還是()依個人喜好,統一就好;

  9、return不要加();

  10、水平/垂直留白不要濫用,怎麼易讀怎麼來。 

寫在最後

  總的來說,這套程式碼規範還是相當不錯的,既有防止錯誤使用C++的某些特性而導致bugs的規範,又有程式碼書寫的相關規範使其便於閱讀,建議搞C++的童鞋都看一看。當然,具體的團隊應該會有具體的程式碼規範,程式碼風格方面大家可能會有一些區別;不使用C++某些特性(比如不使用C++異常,禁止使用函式預設引數)方面,應該按照具體情況進行折中處理,而不應該生搬硬套程式碼規範;但是“不將字串常量耦合到程式碼中”這種規範,是大家必須遵守的。