I 關於編碼規范
1.最重要的一點:明確規范,然後保持一致。BE CONSISTENT!
II Header Files
1.The #define Guard
Guard的命名規則為<PROJECT>_<PATH>_<FILE>_H_。比如foo工程中的文件foo/src/bar/baz.h應定義如下Guard:
#ifndef FOO_BAR_BAZ_H_
#define FOO_BAR_BAZ_H_
...
#endif // FOO_BAR_BAZ_H_
2.頭文件依賴
在頭文件中盡量使用forward declaration替代#include,這樣可以減少頭文件之間的相互依賴,從而可能使得增量編譯時需要重新編譯的編譯單元減少。
如果要在一個頭文件中使用類Foo,則在以下情況下,可以僅使用forward declaration:
1)聲明類型為Foo *或Foo &的數據成員。
2)聲明參數或返回類型為Foo的函數。
3)聲明類型為Foo的靜態數據成員。因為靜態數據成員是在類定義之外定義的。
如果在源文件(.cpp或.cc)中需要使用符號Foo,應該直接在源文件中使用#include引入Foo的定義,而不是依賴於其它頭文件的間接包含。一個例外是,如果Foo在myfile.cc中使用,可以在myfile.h中包含相應頭文件。
3.內聯函數
只有在函數足夠小時才定義內聯函數,比如小於10行。
不要把析構函數聲明為內聯函數,因為析構函數往往比你編寫的要長得多,因為編譯器會插入相關成員的析構操作。
不要將函數有loop和switch的函數聲明的內聯函數。
有些函數即使聲明為內聯函數也不會內聯,比如虛函數和遞歸函數。
4.The -inl.h Files
由於內聯函數需要在頭文件中實現,這樣編譯器才能在相應函數被調用的地方插入內聯函數實現代碼。
對於簡單的內聯函數,比如accessors和mutators,應該在.h文件中定義。
更復雜的內聯函數應該在單獨的-inl.h文件中定義。
使用-inl.h文件的另一種情況是函數模板的定義,從而使得你的模板定義更具可讀性。
5.函數參數的順序
C++的函數參數有三種類型:input, output, or both. 規則是將輸入參數放在輸出參數之前。
通常輸入參數應該是值類型或const引用,輸出參數是非const指針。
6.Names and Order of Includes
頭文件應分組包含,通常以如下順序包含頭文件:類定義頭文件,C庫,C++庫,其它三方庫,工程定義頭文件。
所有工程定義的頭文件,應該以工程源碼根目錄為起點,列出完整路徑,絕不應該使用"."和".."來表示以當前目錄為基准的相對路徑。
例:
#include "foo/public/fooserver.h" // Preferred location.
#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"
III Scoping
1.命名空間
命名空間定義結束時應使用注釋標明對應的命名空間。
1.1 Unnamed namespaces
鼓勵在.cc文件中使用Unnamed namespaces,這樣可以防止運行時命名沖突。通常將局部於.cc文件的一些helper函數放在Unnamed namespace中。
絕不在.h文件中使用Unnamed namespace.
namespace { // This is in a .cc file.
// The content of a namespace is not indented
enum { kUnused, kEOF, kError }; // Commonly used tokens.
bool AtEof() { return pos_ == kEOF; } // Uses our namespace's EOF.
} // namespace
1.2 Named namespaces
在頭文件和forward declarations之後,命名空間包含整個源文件。
絕不使用using指令導入整個命名空間,以免造成命名空間污染。可以使用using導入單個符號。
using指令可以出現在.cc文件中的任何位置,以及頭文件的函數、方法和類中。
1.3 Namespace alias
命名空間別名可以出現在.cc文件中的任何位置,頭文件中包含整個頭文件的命名空間內,以及頭文件中的函數和方法中。
如果在.h中使用了命名空間別名,則包含此頭文件的文件也可以見到此別名,因此在頭文件中使用命名空間別名應慎重。
// Shorten access to some commonly used names in .cc files.
namespace fbz = ::foo::bar::baz;
// Shorten access to some commonly used names (in a .h file).
namespace librarian {
// The following alias is available to all files including
// this header (in namespace librarian):
// alias names should therefore be chosen consistently
// within a project.
namespace pd_s = ::pipeline_diagnostics::sidetable;
inline void my_inline_function() {
// namespace alias local to a function (or method).
namespace fbz = ::foo::bar::baz;
...
}
} // namespace librarian
2.嵌套類
不要使用public nested class,除非你確定它是接口的一部分。這種情況下直接定義這個類(不定義為嵌套類)通常更直觀。更進一步,可以使用命令空間來達到將這個類和全局作用域隔離的目的。
3.Nonmember, Static Member, and Global Functions
有一些函數不屬於任何類或者不適合作為某個類的成員,則最好的處理方式是將其定義為非成員函數,並聲明一個命名空間來管理這些函數。不要僅為了組織一組函數而定義一個類。
如果這些函數只在當前.cc文件中使用,則可以使用unnamed namespace或者聲明為static linkage。
如果這些函數共享某些靜態數據,則應該將它們定義為一個新的類的靜態成員函數。
盡量不使用全局函數。
4.局部變量
讓局部變量的作用域盡可能小,並且在定義的時候初始化,而不是先定義再賦值。
應該在使用變量之前才定義變量,不使用以前C風格的在函數一開始定義所有局部變量的方式。
5.靜態變量和全局變量
絕不使用類類型的靜態變量或全局變量,因為對象創建和銷毀順序的不確定性往往造成難以發現的bug。
具有靜態存儲 (static storage duration)的對象,包括靜態變量、全局變量、類的靜態成員變量和函數內的靜態變量,都應該是“平凡數據對象 (POD, Plain Old Data)”:ints, chars, floats, 指針或者以 POD為元素的arrays/structs 。
C++規范沒有完備地說明靜態成員初始化式的調用順序,因此不要用函數調用來初始化靜態POD變量,除非調用的函數不依賴於其它全局的東西。
對象的銷毀順序與創建順序相反,因為創建順序不確定,所以銷毀的順序也是不確定的(例如,在程序結束時,一個靜態變量可能已經被銷毀了,但是代碼依然在執行(可能在另一個線程中)並且試圖訪問這個靜態變量)。結果是靜態變量只允許為POD,這一規則完全排除了將vector(使用C風格數組)和string(使用const char[])作為靜態變量使用。
如果一定要將一個類對象作為靜態或全局的,應該使用指針(不會被釋放),然後在main函數初始化(或者使用pthread_once()初始化)。要注意這個指針應該是“平凡”的指針(raw pointer),而不是智能指針(smart pointer)。