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