天天看點

Google C++ Style Guide

最近把 Google C++ Style Guide 從頭到尾看了一遍,把相應的内容記錄在這裡,也規範下以後自己的代碼。

Google C++ Style Guide

Header Files

通常每一個

.cpp

檔案都應該有一個對應的

.h

頭檔案,對于一個頭檔案來說(1)需要Self-contained,即一個頭檔案應該有相應的#define 保護、并包含所有他需要的其它頭檔案;(2)盡量應使用頭檔案而不是前置聲明;(3)不能濫用内聯函數隻在函數少于10行的時候才使用内聯函數;(4)使用标準的頭檔案包含順序來增強可讀性,避免隐藏依賴。

  • #define保護的格式為

    #define <PROJECT>_<PATH>_<FILE>_H_

  • 頭檔案的包含順序應該為:
    1. 優先位置,目前檔案需要實作或測試的頭檔案
    2. C 系統檔案
    3. C++ 系統檔案
    4. 其他庫的 .h 檔案
    5. 本項目内 .h 檔案

Scoping

作用域主要規範了

名稱空間

非成員函數

靜态成員函數

全局函數

局部變量

靜态和全局變量

的使用規範:

  • 名稱空間
    • 推薦在

      .cpp

      檔案中使用匿名名稱空間
    • 不能在

      .h

      檔案中使用匿名名稱空間
    • 不要在名稱空間

      std

      内聲明任何東西
    • 不要使用

      using

      包含整個命名空間
    • 不要使用内聯命名空間
    • 可以使用

      using

      聲明
    • 可以使用名稱空間别名
  • 使用靜态成員函數或名字空間内的非成員函數, 盡量不要用裸的全局函數。
  • 将函數變量盡可能置于最小作用域内, 并在變量聲明時進行初始化。
  • 禁止使用

    class

    類型的靜态或全局變量:它們會導緻難以發現的 bug 和不确定的構造和析構函數調用順序。不過

    constexpr

    變量除外,畢竟它們又不涉及動态初始化或析構。

Classes

在使用類的時候應遵循以下規則:

  • 不要在構造函數中進行複雜的初始化 (尤其是那些有可能失敗或者需要調用虛函數的初始化)。
  • 對單個參數的構造函數使用 C++ 關鍵字

    explicit

    .不要定義隐式轉換。
  • 如果你的類型需要, 就讓它們支援拷貝 / 移動. 否則, 就把隐式産生的拷貝和移動函數禁用。
  • 僅當隻有資料時使用

    struct

    , 其它一概使用

    class

  • 使用組合 (

    composition

    ) 常常比使用繼承更合理. 如果使用繼承的話, 定義為

    public

    繼承。
  • 隻在以下情況我們才允許多重繼承: 最多隻有一個基類是非抽象類; 其它基類都是以

    Interface

    為字尾的 純接口類。
  • 接口是指滿足特定條件的類, 這些類以

    Interface

    為字尾 (不強制)。
  • 除少數特定環境外,不要重載運算符。
  • 将所有資料成員聲明為

    private

    , 并根據需要提供相應的存取函數,一般在頭檔案中把存取函數定義成内聯函數。
  • 在類中使用特定的聲明順序:

    public

    private

    之前, 成員函數在資料成員 (變量) 前;

    每個區段内的聲明通常按以下順序:

    1. typedefs

      和枚舉
    2. 常量
    3. 構造函數
    4. 析構函數
    5. 成員函數, 含靜态成員函數
    6. 資料成員, 含靜态資料成員

      友元聲明應該放在

      private

      區段. 如果用宏

      DISALLOW_COPY_AND_ASSIGN

      禁用拷貝和指派, 應當将其置于

      private

      區段的末尾, 也即整個類聲明的末尾.
  • .cc

    檔案中函數的定義應盡可能和聲明順序一緻。

Functions

在定義函數時,參數順序應該是:先輸入,後輸出。所有按引用傳遞的參數必須加上

const

。傾向編寫簡短, 凝練的函數,函數一般不應超過40行。重載函數時最好在函數名裡加入參數資訊。除以下情況,最好不要使用預設參數:

  • 其一,位于 .cc 檔案裡的靜态函數或匿名空間函數,畢竟都隻能在局部檔案裡調用該函數了。
  • 其二,可以在構造函數裡用預設參數,畢竟不可能取得它們的位址。
  • 其三,可以用來模拟變長數組。

Other C++ Features

Google設定的C++的新特性規範有很多,從中選取了以下幾條:

  • Casting

    使用 C++ 的類型轉換, 如 static_cast<>(). 不要使用 int y = (int)x 或 int y = int(x) 等轉換方式;
  • Streams

    隻在記錄日志時使用流.
  • Preincrement and Predecrement

    對于疊代器和其他模闆對象使用字首形式 (

    ++i

    ) 的自增, 自減運算符.
  • Use of const

    const

    變量, 資料成員, 函數和參數為編譯時類型檢測增加了一層保障; 便于盡早發現錯誤。在任何可能的情況下使用 const:
    • 如果函數不會修改傳你入的引用或指針類型參數, 該參數應聲明為 const.
    • 盡可能将函數聲明為 const. 通路函數應該總是 const. 其他不會修改任何資料成員, 未調用非 const 函數, 不會傳回資料成員非 const 指針或引用的函數也應該聲明成 const.
    • 如果資料成員在對象構造之後不再發生變化, 可将其定義為 const.
  • Use of constexpr

    在 C++11 裡,用

    constexpr

    來定義真正的常量,或實作常量初始化。
  • Integer Types

    C++ 内建整型中, 僅使用

    int

    。在需要確定整型大小時, 可以使用

    <stdint.h>

    中長度精确的整型。
  • Preprocessor Macros

    如果要使用宏, 盡可能遵守:
    • 不要在 .h 檔案中定義宏.
    • 在馬上要使用時才進行 #define, 使用後要立即 #undef.
    • 不要隻是對已經存在的宏使用#undef,選擇一個不會沖突的名稱;
    • 不要試圖使用展開後會導緻 C++ 構造不穩定的宏, 不然也至少要附上文檔說明其行為.
    • 不要用 ## 處理函數,類和變量的名字。
  • nullptr/NULL/0

    整數用 , 實數用

    0.0

    , 指針用

    nullptr

    NUL

    , 字元 (串) 用

    '\0'

    .
  • sizeof

    盡可能用 sizeof(varname) 代替 sizeof(type).
  • auto

    auto

    繞過煩瑣的類型名,隻要可讀性好就繼續用,别用在局部變量之外的地方。

Naming

命名的一緻性是規則代碼最鮮明的展現,規則的命名可以快速的讓閱讀者獲知名字代表的是函數還是類型還是常量,命名需要遵守的最基本的原則是

要有描述性;少用縮寫

。對不同的類型,以不同的方式進行命名,規則如下:

  • 檔案名要全部小寫, 可以包含下劃線 (

    _

    ) 或連字元 (

    -

    )。

    _

    更好:

    my_useful_class.cc

  • 類型名稱(類, 結構體, 類型定義 (typedef), 枚舉)的每個單詞首字母均大寫, 不包含下劃線:

    MyExcitingClass

    ,

    MyExcitingEnum

  • 變量名一律小寫, 單詞之間用下劃線連接配接. 類的成員變量以下劃線結尾, 但結構體的就不用,如:

    a_local_variable

    ,

    a_struct_data_member

    ,

    a_class_data_member_

  • 在全局或類裡的常量名稱前加

    k

    :

    kDaysInAWeek

    。且除去開頭的 k 之外每個單詞開頭字母均大寫。
  • 正常函數使用大小寫混合, 取值和設值函數則要求與變量名比對:

    MyExcitingFunction()

    ,

    MyExcitingMethod()

    ,

    my_exciting_member_variable()

    ,

    set_my_exciting_member_variable()

  • 名字空間用小寫字母命名, 并基于項目名稱和目錄結構:

    google_awesome_project

  • 枚舉的命名應當和常量或宏一緻:

    kEnumName

    或是

    ENUM_NAME

  • 如果你一定要用宏, 像這樣命名:

    MY_MACRO_THAT_SCARES_SMALL_CHILDREN

Comments

注釋雖然寫起來很痛苦, 但對保證代碼可讀性至關重要。但同時需要記住的是: 注釋固然很重要, 但

最好的代碼本身應該是自文檔化

。有意義的類型名和變量名, 要遠勝過要用注釋解釋的含糊不清的名字。注釋主要有以下7類:

1. 檔案注釋

在每一個檔案開頭加入版權公告, 然後是檔案内容描述。每個檔案都應該包含以下項, 依次是:

  • 版權聲明 (比如,

    Copyright 2008 Google Inc.

    )
  • 許可證. 為項目選擇合适的許可證版本 (比如,

    Apache 2.0

    ,

    BSD

    ,

    LGPL

    ,

    GPL

    )
  • 作者: 辨別檔案的原始作者.
  • 檔案内容:緊接着版權許可和作者資訊之後, 每個檔案都要用注釋描述檔案内容。

    通常,

    .h

    檔案要對所聲明的類的功能和用法作簡單說明.

    .cc

    檔案通常包含了更多的實作細節或算法技巧讨論。

2. 類注釋

每個類的定義都要附帶一份注釋, 描述類的功能和用法.

3. 函數注釋

函數聲明處注釋描述函數功能; 定義處描述函數實作.

  • 函數聲明:
    • 函數的輸入輸出.
    • 對類成員函數而言: 函數調用期間對象是否需要保持引用參數, 是否會釋放這些參數.
    • 如果函數配置設定了空間, 需要由調用者釋放.
    • 參數是否可以為 NULL.
    • 是否存在函數使用上的性能隐患.
    • 如果函數是可重入的, 其同步前提是什麼?
  • 函數定義:

    每個函數定義時要用注釋說明函數功能和實作要點. 比如說說你用的程式設計技巧, 實作的大緻步驟, 或解釋如此實作的理由, 為什麼前半部分要加鎖而後半部分不需要.

4. 變量注釋

通常變量名本身足以很好說明變量用途. 某些情況下, 也需要額外的注釋說明.

  • 類資料成員:每個類資料成員 (也叫執行個體變量或成員變量) 都應該用注釋說明用途. 如果變量可以接受 NULL 或 -1 等警戒值, 須加以說明。
  • 全局變量:和資料成員一樣, 所有全局變量也要注釋說明含義及用途。

5. 實作注釋

對于代碼中巧妙的, 晦澀的, 有趣的, 重要的地方加以注釋。

  • 比較隐晦的地方要在行尾加入注釋. 在行尾空兩格進行注釋。
  • 如果你需要連續進行多行注釋, 可以使之對齊獲得更好的可讀性。
  • 向函數傳入 NULL, 布爾值或整數時, 要注釋說明含義, 或使用常量讓代碼望文知意。
  • 永遠不要用自然語言翻譯代碼作為注釋. 要假設讀代碼的人 C++ 水準比你高, 即便他/她可能不知道你的用意。

6.

TODO

注釋

對那些臨時的, 短期的解決方案, 或已經夠好但仍不完美的代碼使用

TODO

注釋。

TODO

注釋要使用全大寫的字元串

TODO

, 在随後的圓括号裡寫上你的大名, 郵件位址, 或其它身份辨別. 冒号是可選的. 主要目的是讓添加注釋的人 (也是可以請求提供更多細節的人) 可根據規範的

TODO

格式進行查找. 添加

TODO

注釋并不意味着你要自己來修正。

7. 棄用注釋

通過棄用注釋(

DEPRECATED comments

)以标記某接口點(

interface points

)已棄用。

Formatting

代碼風格和格式是一個比較個人化的問題, 但整個項目(或一個人的所有項目)服從統一的程式設計風格是很重要的, 隻有這樣才能讓所有人能很輕松的閱讀和了解代碼。

1. 行長度

每一行代碼字元數不超過 80.

2. 非

ASCII

字元

盡量不使用非

ASCII

字元, 使用時必須使用

UTF-8

編碼.

3. 空格還是制表位

隻使用空格, 每次縮進 2 個空格.

4. 函數聲明與定義

傳回類型和函數名在同一行, 參數也盡量放在同一行,如果放不下就對形參分行。

注意以下幾點:

  • 如果傳回類型和函數名在一行放不下,分行。
  • 如果傳回類型那個與函數聲明或定義分行了,不要縮進。
  • 左圓括号總是和函數名在同一行;
  • 函數名和左圓括号間沒有空格;
  • 圓括号與參數間沒有空格;
  • 左大括号總在最後一個參數同一行的末尾處;
  • 如果其它風格規則允許的話,右大括号總是單獨位于函數最後一行,或者與左大括号同一行。
  • 右大括号和左大括号間總是有一個空格;
  • 函數聲明和定義中的所有形參必須有命名且一緻;
  • 所有形參應盡可能對齊;
  • 預設縮進為 2 個空格;
  • 換行後的參數保持 4 個空格的縮進;

5.

Lambda

表達式

其它函數怎麼格式化形參和函數體,

Lambda

表達式就怎麼格式化;捕獲清單同理。

6. 函數調用

要麼一行寫完函數調用,要麼在圓括号裡對參數分行,要麼參數另起一行且縮進四格。如果沒有其它顧慮的話,盡可能精簡行數,比如把多個參數适當地放在同一行裡。函數調用遵循如下形式:

  • 如果同一行放不下,可斷為多行,後面每一行都和第一個實參對齊,左圓括号後和右圓括号前不要留白格:
  • 參數也可以放在次行,縮進四格:
  • 把多個參數放在同一行,是為了減少函數調用所需的行數,除非影響到可讀性。有人認為把每個參數都獨立成行,不僅更好讀,而且友善編輯參數。不過,比起所謂的參數編輯,我們更看重可讀性,且後者比較好辦:
  • 如果一些參數本身就是略複雜的表達式,且降低了可讀性。那麼可以直接建立臨時變量描述該表達式,并傳遞給函數,或者放着不管,補充上注釋:
  • 如果某參數獨立成行,對可讀性更有幫助的話,就這麼辦。
  • 此外,如果一系列參數本身就有一定的結構,可以酌情地按其結構來決定參數格式:

7. 清單初始化格式

您平時怎麼格式化函數調用,就怎麼格式化。

8. 條件語句

傾向于不在圓括号内使用空格. 關鍵字

if

else

另起一行.

對基本條件語句有兩種可以接受的格式. 一種在圓括号和條件之間有空格, 另一種沒有。隻要其中一個分支用了大括号,所有分支都要用上大括号。

C++ if (condition) { 圓括号裡沒空格緊鄰。 ... // 2 空格縮進。 } else { // else 與 if 的右括号同一行。 ... }

9. 循環和開關選擇語句

switch

語句可以使用大括号分段,以表明

cases

之間不是連在一起的。在單語句循環裡,括号可用可不用。空循環體應使用

{}

continue

.

C++ switch (var) { case 0: { // 2 空格縮進 ... // 4 空格縮進 break; } case 1: { ... break; } default: { assert(false); } }

在單語句循環裡,括号可用可不用:

C++ for (int i = 0; i < kSomeNumber; ++i) printf("I love you\n"); for (int i = 0; i < kSomeNumber; ++i) { printf("I take it back\n"); }

空循環體應使用

{}

continue

, 而不是一個簡單的分号.

C++ while (condition) { // 反複循環直到條件失效。 } for (int i = 0; i < kSomeNumber; ++i) {} // 可 - 空循環體。 while (condition) continue; // 可 - contunue 表明沒有邏輯。

10. 指針和引用表達式

句點或箭頭前後不要有空格. 指針/位址操作符 (

*

,

&

) 之後不能有空格.

下面是指針和引用表達式的正确使用範例:

C++ x = *p; p = &x; x = r.y; x = r->y;

11. 布爾表達式

如果一個布爾表達式超過 标準行寬, 斷行方式要統一一下.

下例中, 邏輯與 (

&&

) 操作符總位于行尾:

C++ if (this_one_thing > this_other_thing && a_third_thing == a_fourth_thing && yet_another & last_one) { ... }

12. 函數傳回值

return

表達式裡時沒必要都用圓括号,變量傳回時不要使用圓括号,可以用圓括号把複雜表達式圈起來,改善可讀性。

13. 變量及數組初始化

=

,

()

{}

均可。請務必小心清單初始化

{...}

14. 預處理指令

預處理指令不要縮進, 從行首開始。即使預處理指令位于縮進代碼塊中, 指令也應從行首開始.

C++ // 可 - directives at beginning of line if (lopsided_score) { #if DISASTER_PENDING // 正确 -- 行開頭起。 DropEverything(); #endif BackToNormal(); }

15. 類格式

通路控制塊的聲明依次序是

public:

,

protected:

,

private:

, 每次縮進 1 個空格.

類聲明 (對類注釋不了解的話, 參考 類注釋) 的基本格式如下:

C++ class MyClass : public OtherClass { public: // 注意有 1 空格縮進! MyClass(); // 照常,2 空格縮進。 explicit MyClass(int var); ~MyClass() {} void SomeFunction(); void SomeFunctionThatDoesNothing() { } void set_some_var(int var) { some_var_ = var; } int some_var() const { return some_var_; } private: bool SomeInternalFunction(); int some_var_; int some_other_var_; DISALLOW_COPY_AND_ASSIGN(MyClass); };

注意事項:

  • 所有基類名應在 80 列限制下盡量與子類名放在同一行.
  • 關鍵詞 public:, protected:, private: 要縮進 1 個空格.
  • 除第一個關鍵詞 (一般是 public) 外, 其他關鍵詞前要空一行. 如果類比較小的話也可以不空.
  • 這些關鍵詞後不要保留白行.
  • public 放在最前面, 然後是 protected, 最後是 private.

16. 構造函數初始值清單

構造函數初始值清單放在同一行或按四格縮進并排幾行.

17. 名字空間格式化

名字空間 不要增加額外的縮進層次,

18. 水準留白

水準留白的使用因地制宜. 永遠不要在行尾添加沒意義的留白.

  • 正常
    • 左大括号前恒有空格。
    • 分号前不加空格。
    • 大括号内部可與空格緊鄰也不可,不過兩邊都要加上。
    • 繼承與初始化清單中的冒号前後恒有空格。
    • 内聯函數實作,在大括号内部加上空格并編寫實作。
    • 大括号裡面是空的話,不加空格。
  • 循環和條件語句
    • if 條件語句和循環語句關鍵字後均有空格。
    • 圓括号内部不緊鄰空格。
    • 循環和條件語句的圓括号裡可以與空格緊鄰。
    • 循環裡内

      ;

      後恒有空格,

      前可以加個空格。
    • switch

      case

      的冒号前無空格。
  • 操作符:
    • 指派作業系統前後恒有空格。
    • 其它二進制操作符也前後恒有空格,不過對 factors 前後不加空格也可以。
    • 圓括号内部不緊鄰空格。
    • 在參數和一進制操作符之間不加空格。
  • 模闆和轉換:
    • 尖叫括号(< and >) 不與空格緊鄰,< 前沒有空格,>( 之間也沒有。
    • 在類型與指針操作符之間留白格也可以,但要保持一緻。
    • 您或許可以在 < < 裡加上一對對稱的空格。

19. 垂直留白

垂直留白越少越好.

這不僅僅是規則而是原則問題了: 不在萬不得已, 不要使用空行.

  • 兩個函數定義之間的空行不要超過 2 行
  • 函數體首尾不要留白行, 函數體中也不要随意添加空行.
  • 函數體内開頭或結尾的空行可讀性微乎其微。
  • 在多重 if-else 塊裡加空行或許有點可讀性。

繼續閱讀