google c++ 代碼規範關鍵條目總結

GOOGLE C++代碼規範總結:

開始一個項目之前,首先應該明確編碼規範。一個好的編碼習慣可以讓代碼更易於閱讀,同時也能讓程序員避免犯一些常見的錯誤。

爲了便於快速參考,此帖僅僅提取了關鍵點,具體的詳解請參考官方地址:
英文:Google C++ Style Guide
中文:C++ 風格指南

Content

一、頭文件
二、作用域
三、類
四、函數
五、來自GOOGLE的技巧
六、其他C++特性
七、命名約定
八、註釋
九、格式
十、例外


一、頭文件

  1. 通常每一個 .cc 文件都有一個對應的 .h 文件。
  2. 頭文件應該能夠自給自足(self-contained)。
  3. 所有頭文件都應該使用 #define 來防止頭文件被多重包含, 命名格式是<PROJECT>_<PATH>_<FILE>_H_
  4. 儘可能地避免使用前置聲明。
  5. 只有當函數只有不大於 10 行時纔將其定義爲內聯函數。
  6. 頭文件包含順序:
    1. 相關頭文件 
    2. C 系統文件  
    3. C++ 系統文件  
    4. 其他庫的 .h 文件  
    5. 本項目內 .h 文件  

例子:

#ifndef FOO_BAR_BAZ_H_
#define FOO_BAR_BAZ_H_

#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"

#endif // FOO_BAR_BAZ_H_

二、作用域

  1. 鼓勵在 .cc 文件中使用匿名命名空間或 static 聲明。
  2. 禁止使用 using 指示(using-directive)和內聯命名空間(inline namespace)。
  3. 儘量不要用裸的全局函數,不要用類的靜態方法模擬出命名空間的效果。
  4. 將函數變量儘可能置於最小作用域內(考慮效率的循環體是個例外), 並在變量聲明時進行初始化。
  5. 禁止定義靜態儲存週期非POD變量。

例子:

#include "a.h"

DEFINE_FLAG(bool, someflag, false, "dummy flag");

namespace a {

inline void my_inline_function() { // 左對齊,不要縮進
  // 限制在一個函數中的命名空間別名
  namespace baz = ::foo::bar::baz;
  ...
}              

} // namespace a

三、類

  1. 不要在構造函數中調用虛函數,也不要在無法報出錯誤時進行可能失敗的初始化。
  2. 對於轉換運算符和單參數構造函數, 使用explicit關鍵字。
  3. 如果你的類型需要,就讓它們支持拷貝 / 移動。 否則,就把隱式產生的拷貝和移動函數禁用。
  4. 僅當只有數據成員時使用struct, 其它一概使用class
  5. 優先考慮組合。如果用繼承的話,定義爲public繼承。
  6. 只在以下情況我們才允許多重繼承: 最多隻有一個基類是非抽象類;其它基類都是以 Interface 爲後綴的 純接口類。
  7. 除少數特定環境外,不要重載運算符。也不要創建用戶定義字面量。
  8. 所有數據成員聲明爲private, 除非是static const類型成員。
  9. 類定義一般應以public:開始, 後跟protected:,最後是private:。每部分的內容按如下順序:
    1. 類型 (包括 typedef, using 和嵌套的結構體與類)
    2. 常量
    3. 工廠函數, 構造函數, 賦值運算符, 析構函數, 其它函數(從左向右)
    4. 數據成員
  1. 只有那些普通的, 或性能關鍵且短小的函數可以內聯在類定義中。

四、函數

  1. 通常函數的參數順序爲: 輸入參數在先, 後跟輸出參數。
  2. 傾向於編寫簡短, 凝練的函數(不超過40行)。
  3. 所有引用參數都必須是const。輸入參數是值參或const引用,輸出參數爲指針。輸入參數可以是const指針, 但決不能是非const的引用參數, 除非特殊要求(如swap)。
  4. 函數重載要讓人一目瞭然。比如用 AppendString() 和 AppendInt() 等代替重載多個 Append()。
  5. 只允許在非虛函數中使用缺省參數,且必須保證缺省參數的值始終一致。一般情況下建議使用函數重載。如果在每個調用點缺省參數的值都有可能不同, 缺省函數也不允許使用。
  6. 只有在常規寫法 (返回類型前置) 不便於書寫或不便於閱讀時使用返回類型後置語法。

五、來自GOOGLE的技巧

  1. 動態分配出的對象最好有單一且固定的所有主, 並通過智能指針傳遞所有權。
  2. 使用cpplint.py檢查風格錯誤。

六、其他C++特性

  1. 只在定義移動構造函數與移動賦值操作時使用右值引用. 不要使用 std::forward。
  2. 不允許使用變長數組和 alloca()。
  3. 通常友元應該定義在同一文件內, 避免代碼讀者跑到其它文件查找使用該私有成員的類。
  4. 不使用 C++ 異常。
  5. 禁止使用 RTTI。
  6. 不要使用 C 風格類型轉換,而應該使用 C++ 風格。
  7. 只在記錄日誌時使用流。
  8. 對於迭代器和其他模板對象使用前綴形式 (++i) 的自增,自減運算符。
  9. 在任何可能的情況下都要使用 const。對於int const *foo 和 const int* foo,提倡但不強制 const 在前,但要保持代碼的一致性。
  10. 在 C++11 裏,用 constexpr 來定義真正的常量,或實現常量初始化。
  11. C++ 內建整型中, 僅使用 int。如果需要,使用<stdint.h> 中長度精確的整型。
  12. 使用斷言來指出變量爲非負數, 而不是使用無符號型。
  13. 代碼應該對 64 位和 32 位系統友好。參考
  14. 創建 64 位常量時使用 LL 或 ULL 作爲後綴。
  15. 使用宏時要非常謹慎, 儘量以內聯函數, 枚舉和常量代替之。
  16. 整數用 0, 實數用 0.0, 指針用 nullptr 或 NULL, 字符 (串) 用 ‘\0’。
  17. 儘可能用 sizeof(varname) 代替 sizeof(type)。
  18. 用 auto 繞過煩瑣的類型名,只要可讀性好就繼續用,別用在局部變量之外的地方。
  19. 可以用列表初始化。
  20. 適當使用 lambda 表達式,所有捕獲都要顯式寫出來。
  21. 不要使用複雜的模板編程。
  22. 只使用 Boost 中被認可的庫。參考
  23. 適當用 C++11。

七、命名約定

  1. 命名要有描述性,少用縮寫(廣爲人知的縮寫是允許的)。
  2. 代碼文件名要全部小寫,可以包含下劃線 “_” 或連字符 “-”,依照項目的約定. 如果沒有約定, 那麼 “_” 更好。不要使用已經存在於/usr/include下的文件名。
  3. C++ 文件要以 .cc 結尾,頭文件以 .h 結尾。專門插入文本的文件則以 .inc 結尾。
  4. 類型名稱的每個單詞首字母均大寫,不包含下劃線:MyExcitingClass,MyExcitingEnum。
  5. 變量 (包括函數參數) 和數據成員名一律小寫,單詞之間用下劃線連接。類的成員變量以下劃線結尾,但結構體的就不用。
  6. 聲明爲 constexpr 或 const 的變量,或在程序運行期間其值始終保持不變的, 命名時以 “k” 開頭, 大小寫混合,如kDaysInAWeek
  7. 常規函數使用大小寫混合,取值和設值函數則要求與變量名匹配:MyExcitingFunction(),MyExcitingMethod(),my_exciting_member_variable(),set_my_exciting_member_variable()。
  8. 命名空間以小寫字母命名. 最高級命名空間的名字取決於項目名稱。
  9. 枚舉的命名應當和 常量(優先) 或 宏 一致: kEnumName 或是 ENUM_NAME。
  10. 通常不應該使用宏。若要用,用大寫加下劃線。如MY_MACRO。
  11. 如果你命名的實體與已有 C/C++ 實體相似, 可參考現有命名策略。

八、註釋

  1. 使用 // 或 /* */,統一就好。
  2. 在每一個文件開頭加入 版權公告 和 文件內容 信息。
  3. 每個類的定義都要附帶一份註釋, 描述類的功能和用法。
// Iterates over the contents of a GargantuanTable.
// Example:
//    GargantuanTableIterator* iter = table->NewIterator();
//    for (iter->Seek("foo"); !iter->done(); iter->Next()) {
//      process(iter->key(), iter->value());
//    }
//    delete iter;
class GargantuanTableIterator {
  ...
};
  1. 類的聲明和定義分開了(例如分別放在了 .h 和 .cc 文件中),此時,描述類用法的註釋應當和接口定義放在一起,描述類的操作和實現的註釋應當和實現放在一起。不要在 .h 和 .cc 之間複製註釋。
  2. 函數聲明處的註釋描述函數功能,定義處的註釋描述函數實現。註釋使用敘述式 (“Opens the file”) 而非指令式(“Open the file”)。要敘述的內容如下(也要避免囉嗦,顯而易見的就不必指明瞭):
  函數的輸入輸出.
  對類成員函數而言: 函數調用期間對象是否需要保持引用參數, 是否會釋放這些參數.
  函數是否分配了必須由調用者釋放的空間.
  參數是否可以爲空指針.
  是否存在函數使用上的性能隱患.
  如果函數是可重入的, 其同步前提是什麼?
// Returns an iterator for this table.  It is the client's
// responsibility to delete the iterator when it is done with it,
// and it must not use the iterator once the GargantuanTable object
// on which the iterator was created has been deleted.
//
// The iterator is initially positioned at the beginning of the table.
//
// This method is equivalent to:
//    Iterator* iter = table->NewIterator();
//    iter->Seek("");
//    return iter;
// If you are going to immediately seek to another place in the
// returned iterator, it will be faster to use NewIterator()
// and avoid the extra seek.
Iterator* GetIterator() const;
  1. 函數定義處的註釋重點要放在如何實現上。
  2. 如果變量的變量名和類型名不足以說明,就應該用註釋說明其用途。
  3. 在巧妙或者晦澀的代碼段前要加代碼前註釋:
// Divide result by two, taking into account that x
// contains the carry from the add.
for (int i = 0; i < result->size(); i++) {
  x = (x << 8) + (*result)[i];
  (*result)[i] = x >> 1;
  x &= 1;
}
  1. 在巧妙或者晦澀的代碼行後要加行註釋,在行尾空兩格進行註釋或多行時注意對齊:
DoSomething();                  // Comment here so the comments line up.
DoSomethingElseThatIsLonger();  // Two spaces between the code and the comment.
{ // One space before comment when opening a new scope is allowed,
  // thus the comment lines up with the following comments and code.
  DoSomethingElse();  // Two spaces before line comments normally.
}
std::vector<string> list{
                    // Comments in braced lists describe the next element...
                    "First item",
                    // .. and should be aligned appropriately.
"Second item"};
DoSomething(); /* For trailing block comments, one space is fine. */
  1. 對於函數參數,用具名變量代替大段而複雜的嵌套表達式。
  2. 不要描述顯而易見的現象,不要用自然語言翻譯代碼作爲註釋。
  3. 通常是完整的帶結束標點的敘述語句,語法及標點風格要統一。
  4. 對那些臨時的, 短期的解決方案, 或已經夠好但仍不完美的代碼使用 TODO 註釋。
// TODO([email protected]): Use a "*" here for concatenation operator.
// TODO(Zeke) change this to use relations.
// TODO(bug 12345): remove the "Last visitors" feature
  1. 通過棄用註釋(DEPRECATED)以標記某接口點已棄用。

九、格式

  1. 每一行代碼字符數儘量不超過80。
  2. 儘量不使用非 ASCII 字符,使用時必須使用 UTF-8 編碼。
  3. 不應將用戶界面的文本硬編碼到源代碼中。
  4. 縮進只使用空格,每次2個空格。不要在代碼中使用製表符。
  5. 返回類型和函數名在同一行,參數也儘量放在同一行,如果放不下就對形參分行,分行方式與函數調用一致,其餘細節如下:
// 右圓括號和函數名、第一個參數名間沒有空格。
// 返回類型如果與函數名分行的話不縮進。
ReturnType LongClassName::ReallyReallyReallyLongFunctionName(
    Type par_name1,  // 4 space indent
    Type par_name2,
    Type par_name3) { // 右圓括號和左大括號間有一個空格且在同一行
  DoSomething();  // 2 space indent
  ...
} // 單獨一行

// 未被使用的參數如果其用途不明顯的話, 在函數定義處將參數名註釋起來
void Circle::Rotate(double /*radians*/) {} 

// 屬性,和展開爲屬性的宏,寫在函數聲明或定義的最前面
MUST_USE_RESULT bool IsOK();
  1. lambda表達式格式和函數一樣:
int x = 0;
auto add_to_x = [&x](int n) { x += n; };

std::set<int> blacklist = {7, 8, 9};
std::vector<int> digits = {3, 9, 1, 8, 4, 7, 1};
digits.erase(std::remove_if(digits.begin(), digits.end(), [&blacklist](int i) {
               return blacklist.find(i) != blacklist.end();
             }),
             digits.end());
  1. 函數調用格式:
// 在同一行
bool retval = DoSomething(argument1, argument2, argument3);

// 對參數分行
bool retval = DoSomething(averyveryveryverylongargument1,
                          argument2, argument3);

if (...) {
  ...
  ...
  if (...) {
    // 縮進 4 空格對參數分行
    DoSomething(
        argument1, argument2,  // 4 空格縮進
        argument3, argument4);
  }

// 複雜表達式參數,以具名對象方式傳入
int my_heuristic = scores[x] * y + bases[x];
bool retval = DoSomething(my_heuristic, x, y, z);

// 也可以直接用,加上註釋
bool retval = DoSomething(scores[x] * y + bases[x],  // Score heuristic.
                          x, y, z);

// 如果參數有格式,也可以使用參數本身的格式
my_widget.Transform(x1, x2, x3,
                    y1, y2, y3,
                    z1, z2, z3);
  1. 列表初始化格式:
// 一行列表初始化示範.
return {foo, bar};
functioncall({foo, bar});
pair<int, int> p{foo, bar};

// 當不得不斷行時.
SomeFunction(
    {"assume a zero-length name before {"},  // 假設在 { 前有長度爲零的名字.
    some_other_function_parameter);
SomeType variable{
    some, other, values,
    {"assume a zero-length name before {"},  // 假設在 { 前有長度爲零的名字.
    SomeOtherType{
        "Very long string requiring the surrounding breaks.",  // 非常長的字符串, 前後都需要斷行.
        some, other values},
    SomeOtherType{"Slightly shorter string",  // 稍短的字符串.
                  some, other, values}};
SomeType variable{
    "This is too long to fit all in one line"};  // 字符串過長, 因此無法放在同一行.
MyType m = {  // 注意了, 您可以在 { 前斷行.
    superlongvariablename1,
    superlongvariablename2,
    {short, interior, list},
    {interiorwrappinglist,
     interiorwrappinglist2}};
  1. 條件語句格式,重要的是堅持一種並始終保持下去:
if (condition) {  // if後有空格,圓括號裏沒有空格.
  ...  // 2 空格縮進.
} else if (...) {  // else 與 if 的右括號同一行.
  ...
} else {
  ...
}

if (x == kFoo) return new Foo(); // 簡短的情況,可以不用大括號

if (condition)
  DoSomething();  // 2 空格縮進.

// 只要其中一個分支用了大括號, 兩個分支都要用上大括號.
if (condition) {
  foo;
} else {
  bar;
}
  1. 循環語句和switch語句:
switch (var) {
  case 0: {  // 2 空格縮進
    ...      // 4 空格縮進
    break;
  }
  case 1: {  //大括號可以用可以不用
    ...
    break;
  }
  default: {
    assert(false);  // 如果永遠執行不到這裏,就加一條assert
  }
}

// 循環體單行語句可以不用大括號
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");
}

while (condition) {
  // 反覆循環直到條件失效.
}
for (int i = 0; i < kSomeNumber; ++i) {}  // 好 - 空循環體.
while (condition) continue;  // 好 - contunue 表明沒有邏輯.
while (condition);  // 差 - 看起來僅僅只是 while/loop 的部分之一.
  1. ".“或”->"前後不要有空格. 指針/取地址操作符 (*, &) 之後不能有空格。
x = *p;
p = &x;
x = r.y;
x = r->y;

// 好, 空格前置.
char *c;
const string &str;

// 好, 空格後置.
char* c;
const string& str;
int x, *y;  // 不允許 - 在多重聲明中不能使用 & 或 *
char * c;  // 差 - * 兩邊都有空格
const string & str;  // 差 - & 兩邊都有空格.
  1. 布爾表達式格式:
if (this_one_thing > this_other_thing &&
    a_third_thing == a_fourth_thing &&
    yet_another && last_one) {
  ...
}
  1. return語句格式:
return result;                  // 返回值很簡單, 沒有圓括號.

// 可以用圓括號把複雜表達式圈起來, 改善可讀性.
return (some_long_condition &&
        another_condition);

return (value);                // 差 - 畢竟您從來不會寫 var = (value);
return(result);                // 差 - return 可不是函數!
  1. 變量初始化:
// 以下都可以
int x = 3;
int x(3);
int x{3};
string name("Some Name");
string name = "Some Name";
string name{"Some Name"};
  1. 預處理指令從行首開始:
  if (lopsided_score) {
#if DISASTER_PENDING      // 正確 - 從行首開始
    DropEverything();
# if NOTIFY               // 非必要 - # 後跟空格
    NotifyClient();
# endif
#endif
    BackToNormal();
  }
  1. 訪問控制塊的聲明依次序是 public:, protected:, private:, 每個都縮進 1 個空格:
class MyClass : public OtherClass {
 public:      // 注意有一個空格的縮進
  MyClass();  // 標準的兩空格縮進
  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_;
};
  1. 構造函數初始化列表:
// 如果所有變量能放在同一行:
MyClass::MyClass(int var) : some_var_(var) {
  DoSomething();
}

// 如果不能放在同一行,
// 必須置於冒號後, 並縮進 4 個空格
MyClass::MyClass(int var)
    : some_var_(var), some_other_var_(var + 1) {
  DoSomething();
}

// 如果初始化列表需要置於多行, 將每一個成員放在單獨的一行
// 並逐行對齊
MyClass::MyClass(int var)
    : some_var_(var),             // 4 space indent
      some_other_var_(var + 1) {  // lined up
  DoSomething();
}

// 右大括號 } 可以和左大括號 { 放在同一行
// 如果這樣做合適的話
MyClass::MyClass(int var)
    : some_var_(var) {}
  1. 命名空間內容不縮進。聲明嵌套命名空間時,每個命名空間都獨立成行。
  2. 行尾不要加多餘的空格。
  3. 操作符:
// 賦值運算符前後總是有空格.
x = 0;

// 其它二元操作符也前後恆有空格, 不過對於表達式的子式可以不加空格.
// 圓括號內部沒有緊鄰空格.
v = w * x + y / z;
v = w*x + y/z;
v = w * (x + z);

// 在參數和一元操作符之間不加空格.
x = -5;
++x;
if (x && !y)
  ...
  1. 不在萬不得已,不要使用空行。兩函數之間的空行不要超過兩行。

十、例外

  1. 已有既定風格的項目。
  2. Windows代碼。
2020/05/07 22:57
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章