Tip
所以按引用傳遞的參數(shù)必須加上
const
.
定義:在 C 語言中, 如果函數(shù)需要修改變量的值, 參數(shù)必須為指針, 如 int foo(int *pval)
. 在 C++ 中, 函數(shù)還可以聲明引用參數(shù): int foo(int &val)
.優(yōu)點:定義引用參數(shù)防止出現(xiàn) (*pval)++
這樣丑陋的代碼. 像拷貝構造函數(shù)這樣的應用也是必需的. 而且更明確, 不接受 NULL
指針.缺點:容易引起誤解, 因為引用在語法上是值變量卻擁有指針的語義.結論:函數(shù)參數(shù)列表中, 所有引用參數(shù)都必須是 const
:
void Foo(const string &in, string *out);
事實上這在 Google Code 是一個硬性約定: 輸入參數(shù)是值參或 const
引用, 輸出參數(shù)為指針. 輸入參數(shù)可以是 const
指針, 但決不能是 非 const
的引用參數(shù).
在以下情況你可以把輸入參數(shù)定義為 const
指針: 你想強調參數(shù)不是拷貝而來的, 在對象生存周期內必須一直存在; 最好同時在注釋中詳細說明一下. bind2nd
和 mem_fun
等 STL 適配器不接受引用參數(shù), 這種情況下你也必須把函數(shù)參數(shù)聲明成指針類型.
Tip
僅在輸入參數(shù)類型不同, 功能相同時使用重載函數(shù) (含構造函數(shù)). 不要用函數(shù)重載模擬 缺省函數(shù)參數(shù) .
定義:你可以編寫一個參數(shù)類型為 const string&
的函數(shù), 然后用另一個參數(shù)類型為 const char*
的函數(shù)重載它:
class MyClass {
public:
void Analyze(const string &text);
void Analyze(const char *text, size_t textlen);
};
優(yōu)點:通過重載參數(shù)不同的同名函數(shù), 令代碼更加直觀. 模板化代碼需要重載, 同時為使用者帶來便利.缺點:限制使用重載的一個原因是在某個特定調用點很難確定到底調用的是哪個函數(shù). 另一個原因是當派生類只重載了某個函數(shù)的部分變體, 會令很多人對繼承的語義產生困惑. 此外在閱讀庫的用戶代碼時, 可能會因反對使用 缺省函數(shù)參數(shù) [http://code.google.com/p/google-gflags/] 造成不必要的費解.結論:如果你想重載一個函數(shù), 考慮讓函數(shù)名包含參數(shù)信息, 例如, 使用 AppendString()
, AppendInt()
而不是 Append()
.
Tip
我們不允許使用缺省函數(shù)參數(shù).
優(yōu)點:多數(shù)情況下, 你寫的函數(shù)可能會用到很多的缺省值, 但偶爾你也會修改這些缺省值. 無須為了這些偶爾情況定義很多的函數(shù), 用缺省參數(shù)就能很輕松的做到這點.缺點:大家通常都是通過查看別人的代碼來推斷如何使用 API. 用了缺省參數(shù)的代碼更難維護, 從老代碼復制粘貼而來的新代碼可能只包含部分參數(shù). 當缺省參數(shù)不適用于新代碼時可能會導致重大問題.結論:我們規(guī)定所有參數(shù)必須明確指定, 迫使程序員理解 API 和各參數(shù)值的意義, 避免默默使用他們可能都還沒意識到的缺省參數(shù).
Tip
我們不允許使用變長數(shù)組和
alloca()
.
優(yōu)點:變長數(shù)組具有渾然天成的語法. 變長數(shù)組和 alloca()
也都很高效.缺點:變長數(shù)組和 alloca()
不是標準 C++ 的組成部分. 更重要的是, 它們根據數(shù)據大小動態(tài)分配堆棧內存, 會引起難以發(fā)現(xiàn)的內存越界 bugs: “在我的機器上運行的好好的, 發(fā)布后卻莫名其妙的掛掉了”.結論:使用安全的內存分配器, 如 scoped_ptr
/ scoped_array
.
Tip
我們允許合理的使用友元類及友元函數(shù).
通常友元應該定義在同一文件內, 避免代碼讀者跑到其它文件查找使用該私有成員的類. 經常用到友元的一個地方是將 FooBuilder
聲明為 Foo
的友元, 以便 FooBuilder
正確構造 Foo
的內部狀態(tài), 而無需將該狀態(tài)暴露出來. 某些情況下, 將一個單元測試類聲明成待測類的友元會很方便.
友元擴大了 (但沒有打破) 類的封裝邊界. 某些情況下, 相對于將類成員聲明為 public
, 使用友元是更好的選擇, 尤其是如果你只允許另一個類訪問該類的私有成員時. 當然, 大多數(shù)類都只應該通過其提供的公有成員進行互操作.
Tip
我們不使用 C++ 異常.
優(yōu)點:
Init()
方法替代異常, 但他們分別需要堆分配或新的 “無效” 狀態(tài);缺點:
throw
語句時, 你必須檢查所有調用點. 所有調用點得至少有基本的異常安全保護, 否則永遠捕獲不到異常, 只好 “開心的” 接受程序終止的結果. 例如, 如果 f()
調用了 g()
, g()
又調用了 h()
, h
拋出的異常被 f
捕獲, g
要當心了, 很可能會因疏忽而未被妥善清理.結論:
從表面上看, 使用異常利大于弊, 尤其是在新項目中. 但是對于現(xiàn)有代碼, 引入異常會牽連到所有相關代碼. 如果新項目允許異常向外擴散, 在跟以前未使用異常的代碼整合時也將是個麻煩. 因為 Google 現(xiàn)有的大多數(shù) C++ 代碼都沒有異常處理, 引入帶有異常處理的新代碼相當困難.
鑒于 Google 現(xiàn)有代碼不接受異常, 在現(xiàn)有代碼中使用異常比在新項目中使用的代價多少要大一些. 遷移過程比較慢, 也容易出錯. 我們不相信異常的使用有效替代方案, 如錯誤代碼, 斷言等會造成嚴重負擔.
我們并不是基于哲學或道德層面反對使用異常, 而是在實踐的基礎上. 我們希望在 Google 使用我們自己的開源項目, 但項目中使用異常會為此帶來不便, 因此我們也建議不要在 Google 的開源項目中使用異常. 如果我們需要把這些項目推倒重來顯然不太現(xiàn)實.
對于 Windows 代碼來說, 有個 特例.
(YuleFox 注: 對于異常處理, 顯然不是短短幾句話能夠說清楚的, 以構造函數(shù)為例, 很多 C++ 書籍上都提到當構造失敗時只有異??梢蕴幚? Google 禁止使用異常這一點, 僅僅是為了自身的方便, 說大了, 無非是基于軟件管理成本上, 實際使用中還是自己決定)
Tip
我們禁止使用 RTTI.
定義:RTTI 允許程序員在運行時識別 C++ 類對象的類型.優(yōu)點:
RTTI 在某些單元測試中非常有用. 比如進行工廠類測試時, 用來驗證一個新建對象是否為期望的動態(tài)類型.
除測試外, 極少用到.
缺點:在運行時判斷類型通常意味著設計問題. 如果你需要在運行期間確定一個對象的類型, 這通常說明你需要考慮重新設計你的類.結論:
除單元測試外, 不要使用 RTTI. 如果你發(fā)現(xiàn)自己不得不寫一些行為邏輯取決于對象類型的代碼, 考慮換一種方式判斷對象類型.
如果要實現(xiàn)根據子類類型來確定執(zhí)行不同邏輯代碼, 虛函數(shù)無疑更合適. 在對象內部就可以處理類型識別問題.
如果要在對象外部的代碼中判斷類型, 考慮使用雙重分派方案, 如訪問者模式. 可以方便的在對象本身之外確定類的類型.
如果你認為上面的方法你真的掌握不了, 你可以使用 RTTI, 但務必請三思 :-) . 不要試圖手工實現(xiàn)一個貌似 RTTI 的替代方案, 我們反對使用 RTTI 的理由, 同樣適用于那些在類型繼承體系上使用類型標簽的替代方案.
Tip
使用 C++ 的類型轉換, 如
static_cast<>()
. 不要使用int y = (int)x
或int y = int(x)
等轉換方式;
定義:C++ 采用了有別于 C 的類型轉換機制, 對轉換操作進行歸類.優(yōu)點:C 語言的類型轉換問題在于模棱兩可的操作; 有時是在做強制轉換 (如 (int)3.5
), 有時是在做類型轉換 (如 (int)"hello"
). 另外, C++ 的類型轉換在查找時更醒目.缺點:惡心的語法.結論:
不要使用 C 風格類型轉換. 而應該使用 C++ 風格.
- 用
static_cast
替代 C 風格的值轉換, 或某個類指針需要明確的向上轉換為父類指針時.- 用
const_cast
去掉const
限定符.- 用
reinterpret_cast
指針類型和整型或其它指針之間進行不安全的相互轉換. 僅在你對所做一切了然于心時使用.dynamic_cast
測試代碼以外不要使用. 除非是單元測試, 如果你需要在運行時確定類型信息, 說明有 設計缺陷.
Tip
只在記錄日志時使用流.
定義:流用來替代 printf()
和 scanf()
.優(yōu)點:有了流, 在打印時不需要關心對象的類型. 不用擔心格式化字符串與參數(shù)列表不匹配 (雖然在 gcc 中使用 printf
也不存在這個問題). 流的構造和析構函數(shù)會自動打開和關閉對應的文件.缺點:流使得 pread()
等功能函數(shù)很難執(zhí)行. 如果不使用 printf
風格的格式化字符串, 某些格式化操作 (尤其是常用的格式字符串 %.*s
) 用流處理性能是很低的. 流不支持字符串操作符重新排序 (%1s), 而這一點對于軟件國際化很有用.結論:
不要使用流, 除非是日志接口需要. 使用 printf
之類的代替.
使用流還有很多利弊, 但代碼一致性勝過一切. 不要在代碼中使用流.
拓展討論:
對這一條規(guī)則存在一些爭論, 這兒給出點深層次原因. 回想一下唯一性原則 (Only One Way): 我們希望在任何時候都只使用一種確定的 I/O 類型, 使代碼在所有 I/O 處都保持一致. 因此, 我們不希望用戶來決定是使用流還是 printf + read/write
. 相反, 我們應該決定到底用哪一種方式. 把日志作為特例是因為日志是一個非常獨特的應用, 還有一些是歷史原因.
流的支持者們主張流是不二之選, 但觀點并不是那么清晰有力. 他們指出的流的每個優(yōu)勢也都是其劣勢. 流最大的優(yōu)勢是在輸出時不需要關心打印對象的類型. 這是一個亮點. 同時, 也是一個不足: 你很容易用錯類型, 而編譯器不會報警. 使用流時容易造成的這類錯誤:
cout << this; // Prints the address
cout << *this; // Prints the contents
由于 <<
被重載, 編譯器不會報錯. 就因為這一點我們反對使用操作符重載.
有人說 printf
的格式化丑陋不堪, 易讀性差, 但流也好不到哪兒去. 看看下面兩段代碼吧, 實現(xiàn)相同的功能, 哪個更清晰?
cerr << "Error connecting to '" << foo->bar()->hostname.first
<< ":" << foo->bar()->hostname.second << ": " << strerror(errno);
fprintf(stderr, "Error connecting to '%s:%u: %s",
foo->bar()->hostname.first, foo->bar()->hostname.second,
strerror(errno));
你可能會說, “把流封裝一下就會比較好了”, 這兒可以, 其他地方呢? 而且不要忘了, 我們的目標是使語言更緊湊, 而不是添加一些別人需要學習的新裝備.
每一種方式都是各有利弊, “沒有最好, 只有更適合”. 簡單性原則告誡我們必須從中選擇其一, 最后大多數(shù)決定采用 printf + read/write
.
Tip
對于迭代器和其他模板對象使用前綴形式 (
++i
) 的自增, 自減運算符.
定義:對于變量在自增 (++i
或 i++
) 或自減 (--i
或 i--
) 后表達式的值又沒有沒用到的情況下, 需要確定到底是使用前置還是后置的自增 (自減).優(yōu)點:不考慮返回值的話, 前置自增 (++i
) 通常要比后置自增 (i++
) 效率更高. 因為后置自增 (或自減) 需要對表達式的值 i
進行一次拷貝. 如果 i
是迭代器或其他非數(shù)值類型, 拷貝的代價是比較大的. 既然兩種自增方式實現(xiàn)的功能一樣, 為什么不總是使用前置自增呢?缺點:在 C 開發(fā)中, 當表達式的值未被使用時, 傳統(tǒng)的做法是使用后置自增, 特別是在 for
循環(huán)中. 有些人覺得后置自增更加易懂, 因為這很像自然語言, 主語 (i
) 在謂語動詞 (++
) 前.結論:對簡單數(shù)值 (非對象), 兩種都無所謂. 對迭代器和模板類型, 使用前置自增 (自減).
const
的使用Tip
我們強烈建議你在任何可能的情況下都要使用
const
.
定義:在聲明的變量或參數(shù)前加上關鍵字 const
用于指明變量值不可被篡改 (如 const int foo
). 為類中的函數(shù)加上 const
限定符表明該函數(shù)不會修改類成員變量的狀態(tài) (如 class Foo { int Bar(char c) const; };
).優(yōu)點:大家更容易理解如何使用變量. 編譯器可以更好地進行類型檢測, 相應地, 也能生成更好的代碼. 人們對編寫正確的代碼更加自信, 因為他們知道所調用的函數(shù)被限定了能或不能修改變量值. 即使是在無鎖的多線程編程中, 人們也知道什么樣的函數(shù)是安全的.缺點:const
是入侵性的: 如果你向一個函數(shù)傳入 const
變量, 函數(shù)原型聲明中也必須對應 const
參數(shù) (否則變量需要 const_cast
類型轉換), 在調用庫函數(shù)時顯得尤其麻煩.結論:const
變量, 數(shù)據成員, 函數(shù)和參數(shù)為編譯時類型檢測增加了一層保障; 便于盡早發(fā)現(xiàn)錯誤. 因此, 我們強烈建議在任何可能的情況下使用 const
:
- 如果函數(shù)不會修改傳入的引用或指針類型參數(shù), 該參數(shù)應聲明為
const
.- 盡可能將函數(shù)聲明為
const
. 訪問函數(shù)應該總是const
. 其他不會修改任何數(shù)據成員, 未調用非const
函數(shù), 不會返回數(shù)據成員非const
指針或引用的函數(shù)也應該聲明成const
.- 如果數(shù)據成員在對象構造之后不再發(fā)生變化, 可將其定義為
const
.
然而, 也不要發(fā)了瘋似的使用 const
. 像 const int * const * const x;
就有些過了, 雖然它非常精確的描述了常量 x
. 關注真正有幫助意義的信息: 前面的例子寫成 const int** x
就夠了.
關鍵字 mutable
可以使用, 但是在多線程中是不安全的, 使用時首先要考慮線程安全.
const
的位置:
有人喜歡 int const *foo
形式, 不喜歡 const int* foo
, 他們認為前者更一致因此可讀性也更好: 遵循了 const
總位于其描述的對象之后的原則. 但是一致性原則不適用于此, “不要過度使用” 的聲明可以取消大部分你原本想保持的一致性. 將 const
放在前面才更易讀, 因為在自然語言中形容詞 (const
) 是在名詞 (int
) 之前.
這是說, 我們提倡但不強制 const
在前. 但要保持代碼的一致性! (yospaly 注: 也就是不要在一些地方把 const
寫在類型前面, 在其他地方又寫在后面, 確定一種寫法, 然后保持一致.)
Tip
C++ 內建整型中, 僅使用
int
. 如果程序中需要不同大小的變量, 可以使用<stdint.h>
中長度精確的整型, 如int16_t
.
定義:C++ 沒有指定整型的大小. 通常人們假定 short
是 16 位, int``是 32 位, ``long
是 32 位, long long
是 64 位.優(yōu)點:保持聲明統(tǒng)一.缺點:C++ 中整型大小因編譯器和體系結構的不同而不同.結論:<stdint.h>
定義了 int16_t
, uint32_t
, int64_t
等整型, 在需要確保整型大小時可以使用它們代替 short
, unsigned long long
等. 在 C 整型中, 只使用 int
. 在合適的情況下, 推薦使用標準類型如 size_t
和 ptrdiff_t
.
如果已知整數(shù)不會太大, 我們常常會使用 int
, 如循環(huán)計數(shù). 在類似的情況下使用原生類型 int
. 你可以認為 int
至少為 32 位, 但不要認為它會多于 32
位. 如果需要 64 位整型, 用 int64_t
或 uint64_t
.
對于大整數(shù), 使用 int64_t
.
不要使用 uint32_t
等無符號整型, 除非你是在表示一個位組而不是一個數(shù)值, 或是你需要定義二進制補碼溢出. 尤其是不要為了指出數(shù)值永不會為負, 而使用無符號類型. 相反, 你應該使用斷言來保護數(shù)據.
關于無符號整數(shù):有些人, 包括一些教科書作者, 推薦使用無符號類型表示非負數(shù). 這種做法試圖達到自我文檔化. 但是, 在 C 語言中, 這一優(yōu)點被由其導致的 bug 所淹沒. 看看下面的例子:
for (unsigned int i = foo.Length()-1; i >= 0; --i) ...
上述循環(huán)永遠不會退出! 有時 gcc 會發(fā)現(xiàn)該 bug 并報警, 但大部分情況下都不會. 類似的 bug 還會出現(xiàn)在比較有符合變量和無符號變量時. 主要是 C 的類型提升機制會致使無符號類型的行為出乎你的意料.
因此, 使用斷言來指出變量為非負數(shù), 而不是使用無符號型!
Tip
代碼應該對 64 位和 32 位系統(tǒng)友好. 處理打印, 比較, 結構體對齊時應切記:
對于某些類型, printf()
的指示符在 32 位和 64 位系統(tǒng)上可移植性不是很好. C99 標準定義了一些可移植的格式化指示符. 不幸的是, MSVC 7.1 并非全部支持, 而且標準中也有所遺漏, 所以有時我們不得不自己定義一個丑陋的版本 (頭文件 inttypes.h
仿標準風格):
// printf macros for size_t, in the style of inttypes.h
#ifdef _LP64
#define __PRIS_PREFIX "z"
#else
#define __PRIS_PREFIX
#endif
// Use these macros after a % in a printf format string
// to get correct 32/64 bit behavior, like this:
// size_t size = records.size();
// printf("%"PRIuS"\n", size);
#define PRIdS __PRIS_PREFIX "d"
#define PRIxS __PRIS_PREFIX "x"
#define PRIuS __PRIS_PREFIX "u"
#define PRIXS __PRIS_PREFIX "X"
#define PRIoS __PRIS_PREFIX "o"
類型 | 不要使用 | 使用 | 備注 |
---|---|---|---|
void * (或其他指針類型) |
%lx |
%p |
? |
int64_t |
%qd, %lld |
%"PRId64" |
? |
uint64_t |
%qu, %llu, %llx |
%"PRIu64", %"PRIx64" |
? |
size_t |
%u |
%"PRIuS", %"PRIxS" |
C99 規(guī)定 %zu |
ptrdiff_t |
%d |
%"PRIdS" |
C99 規(guī)定 %zd |
注意
PRI*
宏會被編譯器擴展為獨立字符串. 因此如果使用非常量的格式化字符串, 需要將宏的值而不是宏名插入格式中. 使用PRI*
宏同樣可以在%
后包含長度指示符. 例如,printf("x = %30"PRIuS"\n", x)
在 32 位 Linux 上將被展開為printf("x = %30" "u" "\n", x)
, 編譯器當成printf("x = %30u\n", x)
處理 (yospaly 注: 這在 MSVC 6.0 上行不通, VC 6 編譯器不會自動把引號間隔的多個字符串連接一個長字符串).
記住 sizeof(void *) != sizeof(int)
. 如果需要一個指針大小的整數(shù)要用 intptr_t
.
你要非常小心的對待結構體對齊, 尤其是要持久化到磁盤上的結構體 (yospaly 注: 持久化 - 將數(shù)據按字節(jié)流順序保存在磁盤文件或數(shù)據庫中). 在 64 位系統(tǒng)中, 任何含有 int64_t
/uint64_t
成員的類/結構體, 缺省都以 8 字節(jié)在結尾對齊. 如果 32 位和 64 位代碼要共用持久化的結構體, 需要確保兩種體系結構下的結構體對齊一致. 大多數(shù)編譯器都允許調整結構體對齊. gcc 中可使用 __attribute__((packed))
. MSVC 則提供了 #pragma pack()
和 __declspec(align())
(YuleFox 注, 解決方案的項目屬性里也可以直接設置).
int64_t my_value = 0×123456789LL;
uint64_t my_mask = 3ULL << 48;
如果你確實需要 32 位和 64 位系統(tǒng)具有不同代碼, 可以使用 #ifdef _LP64
指令來切分 32/64 位代碼. (盡量不要這么做, 如果非用不可, 盡量使修改局部化)
Tip
使用宏時要非常謹慎, 盡量以內聯(lián)函數(shù), 枚舉和常量代替之.
宏意味著你和編譯器看到的代碼是不同的. 這可能會導致異常行為, 尤其因為宏具有全局作用域.
值得慶幸的是, C++ 中, 宏不像在 C 中那么必不可少. 以往用宏展開性能關鍵的代碼, 現(xiàn)在可以用內聯(lián)函數(shù)替代. 用宏表示常量可被?const
?變量代替. 用宏 “縮寫” 長變量名可被引用代替. 用宏進行條件編譯... 這個, 千萬別這么做, 會令測試更加痛苦 (#define
?防止頭文件重包含當然是個特例).
宏可以做一些其他技術無法實現(xiàn)的事情, 在一些代碼庫 (尤其是底層庫中) 可以看到宏的某些特性 (如用?#
?字符串化, 用?##
?連接等等). 但在使用前, 仔細考慮一下能不能不使用宏達到同樣的目的.
下面給出的用法模式可以避免使用宏帶來的問題; 如果你要宏, 盡可能遵守:
- 不要在?
.h
?文件中定義宏.- 在馬上要使用時才進行?
#define
, 使用后要立即?#undef
.- 不要只是對已經存在的宏使用#undef,選擇一個不會沖突的名稱;
- 不要試圖使用展開后會導致 C++ 構造不穩(wěn)定的宏, 不然也至少要附上文檔說明其行為.
Tip
整數(shù)用?
0
, 實數(shù)用?0.0
, 指針用?NULL
, 字符 (串) 用?'\0'
.
整數(shù)用?0
, 實數(shù)用?0.0
, 這一點是毫無爭議的.
對于指針 (地址值), 到底是用?0
?還是?NULL
, Bjarne Stroustrup 建議使用最原始的?0
. 我們建議使用看上去像是指針的?NULL
, 事實上一些 C++ 編譯器 (如 gcc 4.1.0) 對?NULL
?進行了特殊的定義, 可以給出有用的警告信息, 尤其是?sizeof(NULL)
?和?sizeof(0)
?不相等的情況.
字符 (串) 用?'\0'
, 不僅類型正確而且可讀性好.
Tip
盡可能用?
sizeof(varname)
?代替?sizeof(type)
.
使用?sizeof(varname)
?是因為當代碼中變量類型改變時會自動更新. 某些情況下?sizeof(type)
?或許有意義, 但還是要盡量避免, 因為它會導致變量類型改變后不能同步.
Struct data;
Struct data; memset(&data, 0, sizeof(data));
Warning
memset(&data, 0, sizeof(Struct));
Tip
只使用 Boost 中被認可的庫.
定義:
Boost 庫集?是一個廣受歡迎, 經過同行鑒定, 免費開源的 C++ 庫集.
優(yōu)點:
Boost代碼質量普遍較高, 可移植性好, 填補了 C++ 標準庫很多空白, 如型別的特性, 更完善的綁定器, 更好的智能指針, 同時還提供了?TR1
?(標準庫擴展) 的實現(xiàn).
缺點:
某些 Boost 庫提倡的編程實踐可讀性差, 比如元編程和其他高級模板技術, 以及過度 “函數(shù)化” 的編程風格.
結論:
為了向閱讀和維護代碼的人員提供更好的可讀性, 我們只允許使用 Boost 一部分經認可的特性子集. 目前允許使用以下庫:
- Compressed Pair?:?
boost/compressed_pair.hpp
- Pointer Container?:?
boost/ptr_container
?(序列化除外)- Array?:?
boost/array.hpp
- The Boost Graph Library (BGL)?:?
boost/graph
?(序列化除外)- Property Map?:?
boost/property_map.hpp
- Iterator?中處理迭代器定義的部分 :?
boost/iterator/iterator_adaptor.hpp
,?boost/iterator/iterator_facade.hpp
, 以及?boost/function_output_iterator.hpp
我們正在積極考慮增加其它 Boost 特性, 所以列表中的規(guī)則將不斷變化.
更多建議: