6. 其他 C++ 特性
6.1. 引用參數(shù)
Tip
所有按引用傳遞的參數(shù)必須加上const.
定義:
在 C 語(yǔ)言中, 如果函數(shù)需要修改變量的值, 參數(shù)必須為指針, 如intfoo(int*pval). 在 C++ 中, 函數(shù)還可以聲明引用參數(shù):intfoo(int&val).
優(yōu)點(diǎn):
定義引用參數(shù)防止出現(xiàn)(*pval)++這樣丑陋的代碼. 像拷貝構(gòu)造函數(shù)這樣的應(yīng)用也是必需的. 而且更明確, 不接受NULL指針.
缺點(diǎn):
容易引起誤解, 因?yàn)橐迷谡Z(yǔ)法上是值變量卻擁有指針的語(yǔ)義.
結(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ù),除非用于交換,比如swap().
有時(shí)候,在輸入形參中用constT*指針比constT&更明智。比如:
您會(huì)傳 null 指針。
函數(shù)要把指針或?qū)Φ刂返囊觅x值給輸入形參。
總之大多時(shí)候輸入形參往往是constT&. 若用constT*說(shuō)明輸入另有處理。所以若您要用constT*, 則應(yīng)有理有據(jù),否則會(huì)害得讀者誤解。
6.2. 右值引用
Tip
只在定義移動(dòng)構(gòu)造函數(shù)與移動(dòng)賦值操作時(shí)使用右值引用. 不要使用std::forward.
定義:
右值引用是一種只能綁定到臨時(shí)對(duì)象的引用的一種, 其語(yǔ)法與傳統(tǒng)的引用語(yǔ)法相似. 例如,voidf(string&&s); 聲明了一個(gè)其參數(shù)是一個(gè)字符串的右值引用的函數(shù).
優(yōu)點(diǎn):
用于定義移動(dòng)構(gòu)造函數(shù) (使用類(lèi)的右值引用進(jìn)行構(gòu)造的函數(shù)) 使得移動(dòng)一個(gè)值而非拷貝之成為可能. 例如, 如果v1是一個(gè)vector
右值引用使得編寫(xiě)通用的函數(shù)封裝來(lái)轉(zhuǎn)發(fā)其參數(shù)到另外一個(gè)函數(shù)成為可能, 無(wú)論其參數(shù)是否是臨時(shí)對(duì)象都能正常工作.
右值引用能實(shí)現(xiàn)可移動(dòng)但不可拷貝的類(lèi)型, 這一特性對(duì)那些在拷貝方面沒(méi)有實(shí)際需求, 但有時(shí)又需要將它們作為函數(shù)參數(shù)傳遞或塞入容器的類(lèi)型很有用.
要高效率地使用某些標(biāo)準(zhǔn)庫(kù)類(lèi)型, 例如std::unique_ptr,std::move是必需的.
缺點(diǎn):
右值引用是一個(gè)相對(duì)比較新的特性 (由 C++11 引入), 它尚未被廣泛理解. 類(lèi)似引用崩潰, 移動(dòng)構(gòu)造函數(shù)的自動(dòng)推導(dǎo)這樣的規(guī)則都是很復(fù)雜的.
結(jié)論:
只在定義移動(dòng)構(gòu)造函數(shù)與移動(dòng)賦值操作時(shí)使用右值引用, 不要使用std::forward功能函數(shù). 你可能會(huì)使用std::move來(lái)表示將值從一個(gè)對(duì)象移動(dòng)而不是復(fù)制到另一個(gè)對(duì)象.
6.3. 函數(shù)重載
Tip
若要用好函數(shù)重載,最好能讓讀者一看調(diào)用點(diǎn)(call site)就胸有成竹,不用花心思猜測(cè)調(diào)用的重載函數(shù)到底是哪一種。該規(guī)則適用于構(gòu)造函數(shù)。
定義:
你可以編寫(xiě)一個(gè)參數(shù)類(lèi)型為conststring&的函數(shù), 然后用另一個(gè)參數(shù)類(lèi)型為constchar*的函數(shù)重載它:
class MyClass { public: void Analyze(const string &text); void Analyze(const char *text, size_t textlen);};
優(yōu)點(diǎn):
通過(guò)重載參數(shù)不同的同名函數(shù), 令代碼更加直觀(guān). 模板化代碼需要重載, 同時(shí)為使用者帶來(lái)便利.
缺點(diǎn):
如果函數(shù)單單靠不同的參數(shù)類(lèi)型而重載(acgtyrant 注:這意味著參數(shù)數(shù)量不變),讀者就得十分熟悉 C++ 五花八門(mén)的匹配規(guī)則,以了解匹配過(guò)程具體到底如何。另外,當(dāng)派生類(lèi)只重載了某個(gè)函數(shù)的部分變體,繼承語(yǔ)義容易令人困惑。
結(jié)論:
如果您打算重載一個(gè)函數(shù), 可以試試改在函數(shù)名里加上參數(shù)信息。例如,用AppendString()和AppendInt()等, 而不是一口氣重載多個(gè)Append().
6.4. 缺省參數(shù)
Tip
我們不允許使用缺省函數(shù)參數(shù),少數(shù)極端情況除外。盡可能改用函數(shù)重載。
優(yōu)點(diǎn):
當(dāng)您有依賴(lài)缺省參數(shù)的函數(shù)時(shí),您也許偶爾會(huì)修改修改這些缺省參數(shù)。通過(guò)缺省參數(shù),不用再為個(gè)別情況而特意定義一大堆函數(shù)了。與函數(shù)重載相比,缺省參數(shù)語(yǔ)法更為清晰,代碼少,也很好地區(qū)分了「必選參數(shù)」和「可選參數(shù)」。
缺點(diǎn):
缺省參數(shù)會(huì)干擾函數(shù)指針,害得后者的函數(shù)簽名(function signature)往往對(duì)不上所實(shí)際要調(diào)用的函數(shù)簽名。即在一個(gè)現(xiàn)有函數(shù)添加缺省參數(shù),就會(huì)改變它的類(lèi)型,那么調(diào)用其地址的代碼可能會(huì)出錯(cuò),不過(guò)函數(shù)重載就沒(méi)這問(wèn)題了。此外,缺省參數(shù)會(huì)造成臃腫的代碼,畢竟它們?cè)诿恳粋€(gè)調(diào)用點(diǎn)(call site)都有重復(fù)(acgtyrant 注:我猜可能是因?yàn)檎{(diào)用函數(shù)的代碼表面上看來(lái)省去了不少參數(shù),但編譯器在編譯時(shí)還是會(huì)在每一個(gè)調(diào)用代碼里統(tǒng)統(tǒng)補(bǔ)上所有默認(rèn)實(shí)參信息,造成大量的重復(fù))。函數(shù)重載正好相反,畢竟它們所謂的「缺省參數(shù)」只會(huì)出現(xiàn)在函數(shù)定義里。
結(jié)論:
由于缺點(diǎn)并不是很?chē)?yán)重,有些人依舊偏愛(ài)缺省參數(shù)勝于函數(shù)重載。所以除了以下情況,我們要求必須顯式提供所有參數(shù)(acgtyrant 注:即不能再通過(guò)缺省參數(shù)來(lái)省略參數(shù)了)。
其一,位于.cc文件里的靜態(tài)函數(shù)或匿名空間函數(shù),畢竟都只能在局部文件里調(diào)用該函數(shù)了。
其二,可以在構(gòu)造函數(shù)里用缺省參數(shù),畢竟不可能取得它們的地址。
其三,可以用來(lái)模擬變長(zhǎng)數(shù)組。
// 通過(guò)空 AlphaNum 以支持四個(gè)形參string StrCat(const AlphaNum &a, const AlphaNum &b = gEmptyAlphaNum, const AlphaNum &c = gEmptyAlphaNum, const AlphaNum &d = gEmptyAlphaNum);
6.5. 變長(zhǎng)數(shù)組和 alloca()
Tip
我們不允許使用變長(zhǎng)數(shù)組和alloca().
優(yōu)點(diǎn):
變長(zhǎng)數(shù)組具有渾然天成的語(yǔ)法. 變長(zhǎng)數(shù)組和alloca()也都很高效.
缺點(diǎn):
變長(zhǎng)數(shù)組和alloca()不是標(biāo)準(zhǔn) C++ 的組成部分. 更重要的是, 它們根據(jù)數(shù)據(jù)大小動(dòng)態(tài)分配堆棧內(nèi)存, 會(huì)引起難以發(fā)現(xiàn)的內(nèi)存越界 bugs: “在我的機(jī)器上運(yùn)行的好好的, 發(fā)布后卻莫名其妙的掛掉了”.
結(jié)論:
改用更安全的分配器(allocator),就像std::vector或std::unique_ptr
6.6. 友元
Tip
我們?cè)试S合理的使用友元類(lèi)及友元函數(shù).
通常友元應(yīng)該定義在同一文件內(nèi), 避免代碼讀者跑到其它文件查找使用該私有成員的類(lèi). 經(jīng)常用到友元的一個(gè)地方是將FooBuilder聲明為Foo的友元, 以便FooBuilder正確構(gòu)造Foo的內(nèi)部狀態(tài), 而無(wú)需將該狀態(tài)暴露出來(lái). 某些情況下, 將一個(gè)單元測(cè)試類(lèi)聲明成待測(cè)類(lèi)的友元會(huì)很方便.
友元擴(kuò)大了 (但沒(méi)有打破) 類(lèi)的封裝邊界. 某些情況下, 相對(duì)于將類(lèi)成員聲明為public, 使用友元是更好的選擇, 尤其是如果你只允許另一個(gè)類(lèi)訪(fǎng)問(wèn)該類(lèi)的私有成員時(shí). 當(dāng)然, 大多數(shù)類(lèi)都只應(yīng)該通過(guò)其提供的公有成員進(jìn)行互操作.
6.7. 異常
Tip
我們不使用 C++ 異常.
優(yōu)點(diǎn):
異常允許應(yīng)用高層決定如何處理在底層嵌套函數(shù)中「不可能發(fā)生」的失?。╢ailures),不用管那些含糊且容易出錯(cuò)的錯(cuò)誤代碼(acgtyrant 注:error code, 我猜是C語(yǔ)言函數(shù)返回的非零 int 值)。
很多現(xiàn)代語(yǔ)言都用異常。引入異常使得 C++ 與 Python, Java 以及其它類(lèi) C++ 的語(yǔ)言更一脈相承。
有些第三方 C++ 庫(kù)依賴(lài)異常,禁用異常就不好用了。
異常是處理構(gòu)造函數(shù)失敗的唯一途徑。雖然可以用工廠(chǎng)函數(shù)(acgtyrant 注:factory function, 出自 C++ 的一種設(shè)計(jì)模式,即「簡(jiǎn)單工廠(chǎng)模式」)或Init()方法代替異常, 但是前者要求在堆棧分配內(nèi)存,后者會(huì)導(dǎo)致剛創(chuàng)建的實(shí)例處于 ”無(wú)效“ 狀態(tài)。
在測(cè)試框架里很好用。
缺點(diǎn):
在現(xiàn)有函數(shù)中添加throw語(yǔ)句時(shí),您必須檢查所有調(diào)用點(diǎn)。要么讓所有調(diào)用點(diǎn)統(tǒng)統(tǒng)具備最低限度的異常安全保證,要么眼睜睜地看異常一路歡快地往上跑,最終中斷掉整個(gè)程序。舉例,f()調(diào)用g(),g()又調(diào)用h(), 且h拋出的異常被f捕獲。當(dāng)心g, 否則會(huì)沒(méi)妥善清理好。
還有更常見(jiàn)的,異常會(huì)徹底擾亂程序的執(zhí)行流程并難以判斷,函數(shù)也許會(huì)在您意料不到的地方返回。您或許會(huì)加一大堆何時(shí)何處處理異常的規(guī)定來(lái)降低風(fēng)險(xiǎn),然而開(kāi)發(fā)者的記憶負(fù)擔(dān)更重了。
異常安全需要RAII和不同的編碼實(shí)踐. 要輕松編寫(xiě)出正確的異常安全代碼需要大量的支持機(jī)制. 更進(jìn)一步地說(shuō), 為了避免讀者理解整個(gè)調(diào)用表, 異常安全必須隔絕從持續(xù)狀態(tài)寫(xiě)到 “提交” 狀態(tài)的邏輯. 這一點(diǎn)有利有弊 (因?yàn)槟阋苍S不得不為了隔離提交而混淆代碼). 如果允許使用異常, 我們就不得不時(shí)刻關(guān)注這樣的弊端, 即使有時(shí)它們并不值得.
啟用異常會(huì)增加二進(jìn)制文件數(shù)據(jù),延長(zhǎng)編譯時(shí)間(或許影響小),還可能加大地址空間的壓力。
濫用異常會(huì)變相鼓勵(lì)開(kāi)發(fā)者去捕捉不合時(shí)宜,或本來(lái)就已經(jīng)沒(méi)法恢復(fù)的「?jìng)萎惓!埂1热纾脩?hù)的輸入不符合格式要求時(shí),也用不著拋異常。如此之類(lèi)的偽異常列都列不完。
結(jié)論:
從表面上看來(lái),使用異常利大于弊, 尤其是在新項(xiàng)目中. 但是對(duì)于現(xiàn)有代碼, 引入異常會(huì)牽連到所有相關(guān)代碼. 如果新項(xiàng)目允許異常向外擴(kuò)散, 在跟以前未使用異常的代碼整合時(shí)也將是個(gè)麻煩. 因?yàn)?Google 現(xiàn)有的大多數(shù) C++ 代碼都沒(méi)有異常處理, 引入帶有異常處理的新代碼相當(dāng)困難.
鑒于 Google 現(xiàn)有代碼不接受異常, 在現(xiàn)有代碼中使用異常比在新項(xiàng)目中使用的代價(jià)多少要大一些. 遷移過(guò)程比較慢, 也容易出錯(cuò). 我們不相信異常的使用有效替代方案, 如錯(cuò)誤代碼, 斷言等會(huì)造成嚴(yán)重負(fù)擔(dān).
我們并不是基于哲學(xué)或道德層面反對(duì)使用異常, 而是在實(shí)踐的基礎(chǔ)上. 我們希望在 Google 使用我們自己的開(kāi)源項(xiàng)目, 但項(xiàng)目中使用異常會(huì)為此帶來(lái)不便, 因此我們也建議不要在 Google 的開(kāi)源項(xiàng)目中使用異常. 如果我們需要把這些項(xiàng)目推倒重來(lái)顯然不太現(xiàn)實(shí).
對(duì)于 Windows 代碼來(lái)說(shuō), 有個(gè)特例.
(YuleFox 注: 對(duì)于異常處理, 顯然不是短短幾句話(huà)能夠說(shuō)清楚的, 以構(gòu)造函數(shù)為例, 很多 C++ 書(shū)籍上都提到當(dāng)構(gòu)造失敗時(shí)只有異常可以處理, Google 禁止使用異常這一點(diǎn), 僅僅是為了自身的方便, 說(shuō)大了, 無(wú)非是基于軟件管理成本上, 實(shí)際使用中還是自己決定)
6.8. 運(yùn)行時(shí)類(lèi)型識(shí)別
TODO
Tip
我們禁止使用 RTTI.
定義:
RTTI 允許程序員在運(yùn)行時(shí)識(shí)別 C++ 類(lèi)對(duì)象的類(lèi)型. 它通過(guò)使用typeid或者dynamic_cast完成.
優(yōu)點(diǎn):
RTTI 的標(biāo)準(zhǔn)替代 (下面將描述) 需要對(duì)有問(wèn)題的類(lèi)層級(jí)進(jìn)行修改或重構(gòu). 有時(shí)這樣的修改并不是我們所想要的, 甚至是不可取的, 尤其是在一個(gè)已經(jīng)廣泛使用的或者成熟的代碼中.
RTTI 在某些單元測(cè)試中非常有用. 比如進(jìn)行工廠(chǎng)類(lèi)測(cè)試時(shí), 用來(lái)驗(yàn)證一個(gè)新建對(duì)象是否為期望的動(dòng)態(tài)類(lèi)型. RTTI 對(duì)于管理對(duì)象和派生對(duì)象的關(guān)系也很有用.
在考慮多個(gè)抽象對(duì)象時(shí) RTTI 也很好用. 例如:
bool Base::Equal(Base* other) = 0;bool Derived::Equal(Base* other) { Derived* that = dynamic_cast
缺點(diǎn):
在運(yùn)行時(shí)判斷類(lèi)型通常意味著設(shè)計(jì)問(wèn)題. 如果你需要在運(yùn)行期間確定一個(gè)對(duì)象的類(lèi)型, 這通常說(shuō)明你需要考慮重新設(shè)計(jì)你的類(lèi).
隨意地使用 RTTI 會(huì)使你的代碼難以維護(hù). 它使得基于類(lèi)型的判斷樹(shù)或者 switch 語(yǔ)句散布在代碼各處. 如果以后要進(jìn)行修改, 你就必須檢查它們.
結(jié)論:
RTTI 有合理的用途但是容易被濫用, 因此在使用時(shí)請(qǐng)務(wù)必注意. 在單元測(cè)試中可以使用 RTTI, 但是在其他代碼中請(qǐng)盡量避免. 尤其是在新代碼中, 使用 RTTI 前務(wù)必三思. 如果你的代碼需要根據(jù)不同的對(duì)象類(lèi)型執(zhí)行不同的行為的話(huà), 請(qǐng)考慮用以下的兩種替代方案之一查詢(xún)類(lèi)型:
虛函數(shù)可以根據(jù)子類(lèi)類(lèi)型的不同而執(zhí)行不同代碼. 這是把工作交給了對(duì)象本身去處理.
如果這一工作需要在對(duì)象之外完成, 可以考慮使用雙重分發(fā)的方案, 例如使用訪(fǎng)問(wèn)者設(shè)計(jì)模式. 這就能夠在對(duì)象之外進(jìn)行類(lèi)型判斷.
如果程序能夠保證給定的基類(lèi)實(shí)例實(shí)際上都是某個(gè)派生類(lèi)的實(shí)例, 那么就可以自由使用 dynamic_cast. 在這種情況下, 使用 dynamic_cast 也是一種替代方案.
基于類(lèi)型的判斷樹(shù)是一個(gè)很強(qiáng)的暗示, 它說(shuō)明你的代碼已經(jīng)偏離正軌了. 不要像下面這樣:
if (typeid(*data) == typeid(D1)) { ...} else if (typeid(*data) == typeid(D2)) { ...} else if (typeid(*data) == typeid(D3)) {...
一旦在類(lèi)層級(jí)中加入新的子類(lèi), 像這樣的代碼往往會(huì)崩潰. 而且, 一旦某個(gè)子類(lèi)的屬性改變了, 你很難找到并修改所有受影響的代碼塊.
不要去手工實(shí)現(xiàn)一個(gè)類(lèi)似 RTTI 的方案. 反對(duì) RTTI 的理由同樣適用于這些方案, 比如帶類(lèi)型標(biāo)簽的類(lèi)繼承體系. 而且, 這些方案會(huì)掩蓋你的真實(shí)意圖.
6.9. 類(lèi)型轉(zhuǎn)換
Tip
使用 C++ 的類(lèi)型轉(zhuǎn)換, 如static_cast<>(). 不要使用inty=(int)x或inty=int(x)等轉(zhuǎn)換方式;
定義:
C++ 采用了有別于 C 的類(lèi)型轉(zhuǎn)換機(jī)制, 對(duì)轉(zhuǎn)換操作進(jìn)行歸類(lèi).
優(yōu)點(diǎn):
C 語(yǔ)言的類(lèi)型轉(zhuǎn)換問(wèn)題在于模棱兩可的操作; 有時(shí)是在做強(qiáng)制轉(zhuǎn)換 (如(int)3.5), 有時(shí)是在做類(lèi)型轉(zhuǎn)換 (如(int)"hello"). 另外, C++ 的類(lèi)型轉(zhuǎn)換在查找時(shí)更醒目.
缺點(diǎn):
惡心的語(yǔ)法.
結(jié)論:
不要使用 C 風(fēng)格類(lèi)型轉(zhuǎn)換. 而應(yīng)該使用 C++ 風(fēng)格.
用static_cast替代 C 風(fēng)格的值轉(zhuǎn)換, 或某個(gè)類(lèi)指針需要明確的向上轉(zhuǎn)換為父類(lèi)指針時(shí).
用const_cast去掉const限定符.
用reinterpret_cast指針類(lèi)型和整型或其它指針之間進(jìn)行不安全的相互轉(zhuǎn)換. 僅在你對(duì)所做一切了然于心時(shí)使用.
至于dynamic_cast參見(jiàn)6.8. 運(yùn)行時(shí)類(lèi)型識(shí)別.
6.10. 流
Tip
只在記錄日志時(shí)使用流.
定義:
流用來(lái)替代printf()和scanf().
優(yōu)點(diǎn):
有了流, 在打印時(shí)不需要關(guān)心對(duì)象的類(lèi)型. 不用擔(dān)心格式化字符串與參數(shù)列表不匹配 (雖然在 gcc 中使用printf也不存在這個(gè)問(wèn)題). 流的構(gòu)造和析構(gòu)函數(shù)會(huì)自動(dòng)打開(kāi)和關(guān)閉對(duì)應(yīng)的文件.
缺點(diǎn):
流使得pread()等功能函數(shù)很難執(zhí)行. 如果不使用printf風(fēng)格的格式化字符串, 某些格式化操作 (尤其是常用的格式字符串%.*s) 用流處理性能是很低的. 流不支持字符串操作符重新排序 (%1s), 而這一點(diǎn)對(duì)于軟件國(guó)際化很有用.
結(jié)論:
不要使用流, 除非是日志接口需要. 使用printf之類(lèi)的代替.
使用流還有很多利弊, 但代碼一致性勝過(guò)一切. 不要在代碼中使用流.
拓展討論:
對(duì)這一條規(guī)則存在一些爭(zhēng)論, 這兒給出點(diǎn)深層次原因. 回想一下唯一性原則 (Only One Way): 我們希望在任何時(shí)候都只使用一種確定的 I/O 類(lèi)型, 使代碼在所有 I/O 處都保持一致. 因此, 我們不希望用戶(hù)來(lái)決定是使用流還是printf+read/write. 相反, 我們應(yīng)該決定到底用哪一種方式. 把日志作為特例是因?yàn)槿罩臼且粋€(gè)非常獨(dú)特的應(yīng)用, 還有一些是歷史原因.
流的支持者們主張流是不二之選, 但觀(guān)點(diǎn)并不是那么清晰有力. 他們指出的流的每個(gè)優(yōu)勢(shì)也都是其劣勢(shì). 流最大的優(yōu)勢(shì)是在輸出時(shí)不需要關(guān)心打印對(duì)象的類(lèi)型. 這是一個(gè)亮點(diǎn). 同時(shí), 也是一個(gè)不足: 你很容易用錯(cuò)類(lèi)型, 而編譯器不會(huì)報(bào)警. 使用流時(shí)容易造成的這類(lèi)錯(cuò)誤:
cout << this; // 輸出地址cout << *this; // 輸出值
由于<
有人說(shuō)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));
你可能會(huì)說(shuō), “把流封裝一下就會(huì)比較好了”, 這兒可以, 其他地方呢? 而且不要忘了, 我們的目標(biāo)是使語(yǔ)言更緊湊, 而不是添加一些別人需要學(xué)習(xí)的新裝備.
每一種方式都是各有利弊, “沒(méi)有最好, 只有更適合”. 簡(jiǎn)單性原則告誡我們必須從中選擇其一, 最后大多數(shù)決定采用printf+read/write.
6.11. 前置自增和自減
Tip
對(duì)于迭代器和其他模板對(duì)象使用前綴形式 (++i) 的自增, 自減運(yùn)算符.
定義:
對(duì)于變量在自增 (++i或i++) 或自減 (--i或i--) 后表達(dá)式的值又沒(méi)有沒(méi)用到的情況下, 需要確定到底是使用前置還是后置的自增 (自減).
優(yōu)點(diǎn):
不考慮返回值的話(huà), 前置自增 (++i) 通常要比后置自增 (i++) 效率更高. 因?yàn)楹笾米栽?(或自減) 需要對(duì)表達(dá)式的值i進(jìn)行一次拷貝. 如果i是迭代器或其他非數(shù)值類(lèi)型, 拷貝的代價(jià)是比較大的. 既然兩種自增方式實(shí)現(xiàn)的功能一樣, 為什么不總是使用前置自增呢?
缺點(diǎn):
在 C 開(kāi)發(fā)中, 當(dāng)表達(dá)式的值未被使用時(shí), 傳統(tǒng)的做法是使用后置自增, 特別是在for循環(huán)中. 有些人覺(jué)得后置自增更加易懂, 因?yàn)檫@很像自然語(yǔ)言, 主語(yǔ) (i) 在謂語(yǔ)動(dòng)詞 (++) 前.
結(jié)論:
對(duì)簡(jiǎn)單數(shù)值 (非對(duì)象), 兩種都無(wú)所謂. 對(duì)迭代器和模板類(lèi)型, 使用前置自增 (自減).
6.12.const用法
Tip
我們強(qiáng)烈建議你在任何可能的情況下都要使用const. 此外有時(shí)改用 C++11 推出的 constexpr 更好。
定義:
在聲明的變量或參數(shù)前加上關(guān)鍵字const用于指明變量值不可被篡改 (如constintfoo). 為類(lèi)中的函數(shù)加上const限定符表明該函數(shù)不會(huì)修改類(lèi)成員變量的狀態(tài) (如classFoo{intBar(charc)const;};).
優(yōu)點(diǎn):
大家更容易理解如何使用變量. 編譯器可以更好地進(jìn)行類(lèi)型檢測(cè), 相應(yīng)地, 也能生成更好的代碼. 人們對(duì)編寫(xiě)正確的代碼更加自信, 因?yàn)樗麄冎浪{(diào)用的函數(shù)被限定了能或不能修改變量值. 即使是在無(wú)鎖的多線(xiàn)程編程中, 人們也知道什么樣的函數(shù)是安全的.
缺點(diǎn):
const是入侵性的: 如果你向一個(gè)函數(shù)傳入const變量, 函數(shù)原型聲明中也必須對(duì)應(yīng)const參數(shù) (否則變量需要const_cast類(lèi)型轉(zhuǎn)換), 在調(diào)用庫(kù)函數(shù)時(shí)顯得尤其麻煩.
結(jié)論:
const變量, 數(shù)據(jù)成員, 函數(shù)和參數(shù)為編譯時(shí)類(lèi)型檢測(cè)增加了一層保障; 便于盡早發(fā)現(xiàn)錯(cuò)誤. 因此, 我們強(qiáng)烈建議在任何可能的情況下使用const:
如果函數(shù)不會(huì)修改傳你入的引用或指針類(lèi)型參數(shù), 該參數(shù)應(yīng)聲明為const.
盡可能將函數(shù)聲明為const. 訪(fǎng)問(wèn)函數(shù)應(yīng)該總是const. 其他不會(huì)修改任何數(shù)據(jù)成員, 未調(diào)用非const函數(shù), 不會(huì)返回?cái)?shù)據(jù)成員非const指針或引用的函數(shù)也應(yīng)該聲明成const.
如果數(shù)據(jù)成員在對(duì)象構(gòu)造之后不再發(fā)生變化, 可將其定義為const.
然而, 也不要發(fā)了瘋似的使用const. 像constint*const*constx;就有些過(guò)了, 雖然它非常精確的描述了常量x. 關(guān)注真正有幫助意義的信息: 前面的例子寫(xiě)成constint**x就夠了.
關(guān)鍵字mutable可以使用, 但是在多線(xiàn)程中是不安全的, 使用時(shí)首先要考慮線(xiàn)程安全.
const的位置:
有人喜歡intconst*foo形式, 不喜歡constint*foo, 他們認(rèn)為前者更一致因此可讀性也更好: 遵循了const總位于其描述的對(duì)象之后的原則. 但是一致性原則不適用于此, “不要過(guò)度使用” 的聲明可以取消大部分你原本想保持的一致性. 將const放在前面才更易讀, 因?yàn)樵谧匀徽Z(yǔ)言中形容詞 (const) 是在名詞 (int) 之前.
這是說(shuō), 我們提倡但不強(qiáng)制const在前. 但要保持代碼的一致性! (Yang.Y 注: 也就是不要在一些地方把const寫(xiě)在類(lèi)型前面, 在其他地方又寫(xiě)在后面, 確定一種寫(xiě)法, 然后保持一致.)
6.13.constexpr用法
Tip
在 C++11 里,用 constexpr 來(lái)定義真正的常量,或?qū)崿F(xiàn)常量初始化。
定義:
變量可以被聲明成 constexpr 以表示它是真正意義上的常量,即在編譯時(shí)和運(yùn)行時(shí)都不變。函數(shù)或構(gòu)造函數(shù)也可以被聲明成 constexpr, 以用來(lái)定義 constexpr 變量。
優(yōu)點(diǎn):
如今 constexpr 就可以定義浮點(diǎn)式的真?常量,不用再依賴(lài)字面值了;也可以定義用戶(hù)自定義類(lèi)型上的常量;甚至也可以定義函數(shù)調(diào)用所返回的常量。
缺點(diǎn):
若過(guò)早把變量?jī)?yōu)化成 constexpr 變量,將來(lái)又要把它改為常規(guī)變量時(shí),挺麻煩的;當(dāng)前對(duì)constexpr函數(shù)和構(gòu)造函數(shù)中允許的限制可能會(huì)導(dǎo)致這些定義中解決的方法模糊。
結(jié)論:
靠 constexpr 特性,方才實(shí)現(xiàn)了 C++ 在接口上打造真正常量機(jī)制的可能。好好用 constexpr 來(lái)定義真?常量以及支持常量的函數(shù)。避免復(fù)雜的函數(shù)定義,以使其能夠與constexpr一起使用。 千萬(wàn)別癡心妄想地想靠 constexpr 來(lái)強(qiáng)制代碼「內(nèi)聯(lián)」。
6.14. 整型
Tip
C++ 內(nèi)建整型中, 僅使用int. 如果程序中需要不同大小的變量, 可以使用
定義:
C++ 沒(méi)有指定整型的大小. 通常人們假定short是 16 位,int是 32 位,long是 32 位,longlong是 64 位.
優(yōu)點(diǎn):
保持聲明統(tǒng)一.
缺點(diǎn):
C++ 中整型大小因編譯器和體系結(jié)構(gòu)的不同而不同.
結(jié)論:
如果已知整數(shù)不會(huì)太大, 我們常常會(huì)使用int, 如循環(huán)計(jì)數(shù). 在類(lèi)似的情況下使用原生類(lèi)型int. 你可以認(rèn)為int至少為 32 位, 但不要認(rèn)為它會(huì)多于32位. 如果需要 64 位整型, 用int64_t或uint64_t.
對(duì)于大整數(shù), 使用int64_t.
不要使用uint32_t等無(wú)符號(hào)整型, 除非你是在表示一個(gè)位組而不是一個(gè)數(shù)值, 或是你需要定義二進(jìn)制補(bǔ)碼溢出. 尤其是不要為了指出數(shù)值永不會(huì)為負(fù), 而使用無(wú)符號(hào)類(lèi)型. 相反, 你應(yīng)該使用斷言來(lái)保護(hù)數(shù)據(jù).
如果您的代碼涉及容器返回的大?。╯ize),確保其類(lèi)型足以應(yīng)付容器各種可能的用法。拿不準(zhǔn)時(shí),類(lèi)型越大越好。
小心整型類(lèi)型轉(zhuǎn)換和整型提升(acgtyrant 注:integer promotions, 比如int與unsignedint運(yùn)算時(shí),前者被提升為unsignedint而有可能溢出),總有意想不到的后果。
關(guān)于無(wú)符號(hào)整數(shù):
有些人, 包括一些教科書(shū)作者, 推薦使用無(wú)符號(hào)類(lèi)型表示非負(fù)數(shù). 這種做法試圖達(dá)到自我文檔化. 但是, 在 C 語(yǔ)言中, 這一優(yōu)點(diǎn)被由其導(dǎo)致的 bug 所淹沒(méi). 看看下面的例子:
for (unsigned int i = foo.Length()-1; i >= 0; --i) ...
上述循環(huán)永遠(yuǎn)不會(huì)退出! 有時(shí) gcc 會(huì)發(fā)現(xiàn)該 bug 并報(bào)警, 但大部分情況下都不會(huì). 類(lèi)似的 bug 還會(huì)出現(xiàn)在比較有符合變量和無(wú)符號(hào)變量時(shí). 主要是 C 的類(lèi)型提升機(jī)制會(huì)致使無(wú)符號(hào)類(lèi)型的行為出乎你的意料.
因此, 使用斷言來(lái)指出變量為非負(fù)數(shù), 而不是使用無(wú)符號(hào)型!
6.15. 64 位下的可移植性
Tip
代碼應(yīng)該對(duì) 64 位和 32 位系統(tǒng)友好. 處理打印, 比較, 結(jié)構(gòu)體對(duì)齊時(shí)應(yīng)切記:
對(duì)于某些類(lèi)型,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"
類(lèi)型 | 不要使用 | 使用 | 備注 |
---|---|---|---|
void*(或其他指針類(lèi)型) | %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*宏會(huì)被編譯器擴(kuò)展為獨(dú)立字符串. 因此如果使用非常量的格式化字符串, 需要將宏的值而不是宏名插入格式中. 使用PRI*宏同樣可以在%后包含長(zhǎng)度指示符. 例如,printf("x=%30"PRIuS"\n",x)在 32 位 Linux 上將被展開(kāi)為printf("x=%30""u""\n",x), 編譯器當(dāng)成printf("x=%30u\n",x)處理 (Yang.Y 注: 這在 MSVC 6.0 上行不通, VC 6 編譯器不會(huì)自動(dòng)把引號(hào)間隔的多個(gè)字符串連接一個(gè)長(zhǎng)字符串).
記住sizeof(void*)!=sizeof(int). 如果需要一個(gè)指針大小的整數(shù)要用intptr_t.
你要非常小心的對(duì)待結(jié)構(gòu)體對(duì)齊, 尤其是要持久化到磁盤(pán)上的結(jié)構(gòu)體 (Yang.Y 注: 持久化 - 將數(shù)據(jù)按字節(jié)流順序保存在磁盤(pán)文件或數(shù)據(jù)庫(kù)中). 在 64 位系統(tǒng)中, 任何含有int64_t/uint64_t成員的類(lèi)/結(jié)構(gòu)體, 缺省都以 8 字節(jié)在結(jié)尾對(duì)齊. 如果 32 位和 64 位代碼要共用持久化的結(jié)構(gòu)體, 需要確保兩種體系結(jié)構(gòu)下的結(jié)構(gòu)體對(duì)齊一致. 大多數(shù)編譯器都允許調(diào)整結(jié)構(gòu)體對(duì)齊. gcc 中可使用__attribute__((packed)). MSVC 則提供了#pragmapack()和__declspec(align())(YuleFox 注, 解決方案的項(xiàng)目屬性里也可以直接設(shè)置).
創(chuàng)建 64 位常量時(shí)使用 LL 或 ULL 作為后綴, 如:
int64_t my_value = 0x123456789LL;uint64_t my_mask = 3ULL << 48;
如果你確實(shí)需要 32 位和 64 位系統(tǒng)具有不同代碼, 可以使用#ifdef_LP64指令來(lái)切分 32/64 位代碼. (盡量不要這么做, 如果非用不可, 盡量使修改局部化)
6.16. 預(yù)處理宏
Tip
使用宏時(shí)要非常謹(jǐn)慎, 盡量以?xún)?nèi)聯(lián)函數(shù), 枚舉和常量代替之.
宏意味著你和編譯器看到的代碼是不同的. 這可能會(huì)導(dǎo)致異常行為, 尤其因?yàn)楹昃哂腥肿饔糜?
值得慶幸的是, C++ 中, 宏不像在 C 中那么必不可少. 以往用宏展開(kāi)性能關(guān)鍵的代碼, 現(xiàn)在可以用內(nèi)聯(lián)函數(shù)替代. 用宏表示常量可被const變量代替. 用宏 “縮寫(xiě)” 長(zhǎng)變量名可被引用代替. 用宏進(jìn)行條件編譯… 這個(gè), 千萬(wàn)別這么做, 會(huì)令測(cè)試更加痛苦 (#define防止頭文件重包含當(dāng)然是個(gè)特例).
宏可以做一些其他技術(shù)無(wú)法實(shí)現(xiàn)的事情, 在一些代碼庫(kù) (尤其是底層庫(kù)中) 可以看到宏的某些特性 (如用#字符串化, 用##連接等等). 但在使用前, 仔細(xì)考慮一下能不能不使用宏達(dá)到同樣的目的.
下面給出的用法模式可以避免使用宏帶來(lái)的問(wèn)題; 如果你要宏, 盡可能遵守:
不要在.h文件中定義宏.
在馬上要使用時(shí)才進(jìn)行#define, 使用后要立即#undef.
不要只是對(duì)已經(jīng)存在的宏使用#undef,選擇一個(gè)不會(huì)沖突的名稱(chēng);
不要試圖使用展開(kāi)后會(huì)導(dǎo)致 C++ 構(gòu)造不穩(wěn)定的宏, 不然也至少要附上文檔說(shuō)明其行為.
不要用##處理函數(shù),類(lèi)和變量的名字。
6.17. 0,nullptr和NULL
Tip
整數(shù)用0, 實(shí)數(shù)用0.0, 指針用nullptr或NULL, 字符 (串) 用'\0'.
整數(shù)用0, 實(shí)數(shù)用0.0, 這一點(diǎn)是毫無(wú)爭(zhēng)議的.
對(duì)于指針 (地址值), 到底是用0,NULL還是nullptr. C++11 項(xiàng)目用nullptr; C++03 項(xiàng)目則用NULL, 畢竟它看起來(lái)像指針。實(shí)際上,一些 C++ 編譯器對(duì)NULL的定義比較特殊,可以輸出有用的警告,特別是sizeof(NULL)就和sizeof(0)不一樣。
字符 (串) 用'\0', 不僅類(lèi)型正確而且可讀性好.
6.18. sizeof
Tip
盡可能用sizeof(varname)代替sizeof(type).
使用sizeof(varname)是因?yàn)楫?dāng)代碼中變量類(lèi)型改變時(shí)會(huì)自動(dòng)更新. 您或許會(huì)用sizeof(type)處理不涉及任何變量的代碼,比如處理來(lái)自外部或內(nèi)部的數(shù)據(jù)格式,這時(shí)用變量就不合適了。
Struct data;Struct data; memset(&data, 0, sizeof(data));
Warning
memset(&data, 0, sizeof(Struct));if (raw_size < sizeof(int)) { LOG(ERROR) << "compressed record not big enough for count: " << raw_size; return false;}
6.19. auto
Tip
用auto繞過(guò)煩瑣的類(lèi)型名,只要可讀性好就繼續(xù)用,別用在局部變量之外的地方。
定義:
C++11 中,若變量被聲明成auto, 那它的類(lèi)型就會(huì)被自動(dòng)匹配成初始化表達(dá)式的類(lèi)型。您可以用auto來(lái)復(fù)制初始化或綁定引用。
vector
優(yōu)點(diǎn):
C++ 類(lèi)型名有時(shí)又長(zhǎng)又臭,特別是涉及模板或命名空間的時(shí)候。就像:
sparse_hash_map
返回類(lèi)型好難讀,代碼目的也不夠一目了然。重構(gòu)其:
auto iter = m.find(val);
好多了。
沒(méi)有auto的話(huà),我們不得不在同一個(gè)表達(dá)式里寫(xiě)同一個(gè)類(lèi)型名兩次,無(wú)謂的重復(fù),就像:
diagnostics::ErrorStatus* status = new diagnostics::ErrorStatus("xyz");
有了 auto, 可以更方便地用中間變量,顯式編寫(xiě)它們的類(lèi)型輕松點(diǎn)。
缺點(diǎn):
類(lèi)型夠明顯時(shí),特別是初始化變量時(shí),代碼才會(huì)夠一目了然。但以下就不一樣了:
auto i = x.Lookup(key);
看不出其類(lèi)型是啥,x 的類(lèi)型聲明恐怕遠(yuǎn)在幾百行之外了。
程序員必須會(huì)區(qū)分auto和constauto&的不同之處,否則會(huì)復(fù)制錯(cuò)東西。
auto 和 C++11 列表初始化的合體令人摸不著頭腦:
auto x(3); // 圓括號(hào)。auto y{3}; // 大括號(hào)。
它們不是同一回事——x是int,y則是std::initializer_list
如果在接口里用auto, 比如聲明頭文件里的一個(gè)常量,那么只要僅僅因?yàn)槌绦騿T一時(shí)修改其值而導(dǎo)致類(lèi)型變化的話(huà)——API 要翻天覆地了。
結(jié)論:
auto只能用在局部變量里用。別用在文件作用域變量,命名空間作用域變量和類(lèi)數(shù)據(jù)成員里。永遠(yuǎn)別列表初始化auto變量。
auto還可以和 C++11 特性「尾置返回類(lèi)型(trailing return type)」一起用,不過(guò)后者只能用在 lambda 表達(dá)式里。
6.20. 列表初始化
Tip
你可以用列表初始化。
早在 C++03 里,聚合類(lèi)型(aggregate types)就已經(jīng)可以被列表初始化了,比如數(shù)組和不自帶構(gòu)造函數(shù)的結(jié)構(gòu)體:
struct Point { int x; int y; };Point p = {1, 2};
C++11 中,該特性得到進(jìn)一步的推廣,任何對(duì)象類(lèi)型都可以被列表初始化。示范如下:
// Vector 接收了一個(gè)初始化列表。vector
用戶(hù)自定義類(lèi)型也可以定義接收std::initializer_list
class MyType { public: // std::initializer_list 專(zhuān)門(mén)接收 init 列表。 // 得以值傳遞。 MyType(std::initializer_list
最后,列表初始化也適用于常規(guī)數(shù)據(jù)類(lèi)型的構(gòu)造,哪怕沒(méi)有接收std::initializer_list
double d{1.23};// MyOtherType 沒(méi)有 std::initializer_list 構(gòu)造函數(shù), // 直接上接收常規(guī)類(lèi)型的構(gòu)造函數(shù)。class MyOtherType { public: explicit MyOtherType(string); MyOtherType(int, string);};MyOtherType m = {1, "b"};// 不過(guò)如果構(gòu)造函數(shù)是顯式的(explict),您就不能用 `= {}` 了。MyOtherType m{"b"};
千萬(wàn)別直接列表初始化 auto 變量,看下一句,估計(jì)沒(méi)人看得懂:
Warning
auto d = {1.23}; // d 即是 std::initializer_list
至于格式化,參見(jiàn)9.7. 列表初始化格式.
6.21. Lambda 表達(dá)式
Tip
適當(dāng)使用 lambda 表達(dá)式。別用默認(rèn) lambda 捕獲,所有捕獲都要顯式寫(xiě)出來(lái)。
定義:
Lambda 表達(dá)式是創(chuàng)建匿名函數(shù)對(duì)象的一種簡(jiǎn)易途徑,常用于把函數(shù)當(dāng)參數(shù)傳,例如:
std::sort(v.begin(), v.end(), [](int x, int y) { return Weight(x) < Weight(y);});
C++11 首次提出 Lambdas, 還提供了一系列處理函數(shù)對(duì)象的工具,比如多態(tài)包裝器(polymorphic wrapper)std::function.
優(yōu)點(diǎn):
傳函數(shù)對(duì)象給 STL 算法,Lambdas 最簡(jiǎn)易,可讀性也好。
Lambdas,std::functions和std::bind可以搭配成通用回調(diào)機(jī)制(general purpose callback mechanism);寫(xiě)接收有界函數(shù)為參數(shù)的函數(shù)也很容易了。
缺點(diǎn):
Lambdas 的變量捕獲略旁門(mén)左道,可能會(huì)造成懸空指針。
Lambdas 可能會(huì)失控;層層嵌套的匿名函數(shù)難以閱讀。
結(jié)論:
按 format 小用 lambda 表達(dá)式怡情。
禁用默認(rèn)捕獲,捕獲都要顯式寫(xiě)出來(lái)。打比方,比起[=](intx){returnx+n;}, 您該寫(xiě)成[n](intx){returnx+n;}才對(duì),這樣讀者也好一眼看出n是被捕獲的值。
匿名函數(shù)始終要簡(jiǎn)短,如果函數(shù)體超過(guò)了五行,那么還不如起名(acgtyrant 注:即把 lambda 表達(dá)式賦值給對(duì)象),或改用函數(shù)。
如果可讀性更好,就顯式寫(xiě)出 lambd 的尾置返回類(lèi)型,就像auto.
6.22. 模板編程
Tip
不要使用復(fù)雜的模板編程
定義:
模板編程指的是利用c++ 模板實(shí)例化機(jī)制是圖靈完備性, 可以被用來(lái)實(shí)現(xiàn)編譯時(shí)刻的類(lèi)型判斷的一系列編程技巧
優(yōu)點(diǎn):
模板編程能夠?qū)崿F(xiàn)非常靈活的類(lèi)型安全的接口和極好的性能, 一些常見(jiàn)的工具比如Google Test, std::tuple, std::function 和 Boost.Spirit. 這些工具如果沒(méi)有模板是實(shí)現(xiàn)不了的
缺點(diǎn):
模板編程所使用的技巧對(duì)于使用c++不是很熟練的人是比較晦澀, 難懂的. 在復(fù)雜的地方使用模板的代碼讓人更不容易讀懂, 并且debug 和 維護(hù)起來(lái)都很麻煩
模板編程經(jīng)常會(huì)導(dǎo)致編譯出錯(cuò)的信息非常不友好: 在代碼出錯(cuò)的時(shí)候, 即使這個(gè)接口非常的簡(jiǎn)單, 模板內(nèi)部復(fù)雜的實(shí)現(xiàn)細(xì)節(jié)也會(huì)在出錯(cuò)信息顯示. 導(dǎo)致這個(gè)編譯出錯(cuò)信息看起來(lái)非常難以理解.
大量的使用模板編程接口會(huì)讓重構(gòu)工具(Visual Assist X, Refactor for C++等等)更難發(fā)揮用途. 首先模板的代碼會(huì)在很多上下文里面擴(kuò)展開(kāi)來(lái), 所以很難確認(rèn)重構(gòu)對(duì)所有的這些展開(kāi)的代碼有用, 其次有些重構(gòu)工具只對(duì)已經(jīng)做過(guò)模板類(lèi)型替換的代碼的AST 有用. 因此重構(gòu)工具對(duì)這些模板實(shí)現(xiàn)的原始代碼并不有效, 很難找出哪些需要重構(gòu).
結(jié)論:
模板編程有時(shí)候能夠?qū)崿F(xiàn)更簡(jiǎn)潔更易用的接口, 但是更多的時(shí)候卻適得其反. 因此模板編程最好只用在少量的基礎(chǔ)組件, 基礎(chǔ)數(shù)據(jù)結(jié)構(gòu)上, 因?yàn)槟0鍘?lái)的額外的維護(hù)成本會(huì)被大量的使用給分擔(dān)掉
在使用模板編程或者其他復(fù)雜的模板技巧的時(shí)候, 你一定要再三考慮一下. 考慮一下你們團(tuán)隊(duì)成員的平均水平是否能夠讀懂并且能夠維護(hù)你寫(xiě)的模板代碼.或者一個(gè)非c++ 程序員和一些只是在出錯(cuò)的時(shí)候偶爾看一下代碼的人能夠讀懂這些錯(cuò)誤信息或者能夠跟蹤函數(shù)的調(diào)用流程. 如果你使用遞歸的模板實(shí)例化, 或者類(lèi)型列表, 或者元函數(shù), 又或者表達(dá)式模板, 或者依賴(lài)SFINAE, 或者sizeof 的trick 手段來(lái)檢查函數(shù)是否重載, 那么這說(shuō)明你模板用的太多了, 這些模板太復(fù)雜了, 我們不推薦使用
如果你使用模板編程, 你必須考慮盡可能的把復(fù)雜度最小化, 并且盡量不要讓模板對(duì)外暴漏. 你最好只在實(shí)現(xiàn)里面使用模板, 然后給用戶(hù)暴露的接口里面并不使用模板, 這樣能提高你的接口的可讀性. 并且你應(yīng)該在這些使用模板的代碼上寫(xiě)盡可能詳細(xì)的注釋. 你的注釋里面應(yīng)該詳細(xì)的包含這些代碼是怎么用的, 這些模板生成出來(lái)的代碼大概是什么樣子的. 還需要額外注意在用戶(hù)錯(cuò)誤使用你的模板代碼的時(shí)候需要輸出更人性化的出錯(cuò)信息. 因?yàn)檫@些出錯(cuò)信息也是你的接口的一部分, 所以你的代碼必須調(diào)整到這些錯(cuò)誤信息在用戶(hù)看起來(lái)應(yīng)該是非常容易理解, 并且用戶(hù)很容易知道如何修改這些錯(cuò)誤
6.23. Boost 庫(kù)
Tip
只使用 Boost 中被認(rèn)可的庫(kù).
定義:
Boost 庫(kù)集是一個(gè)廣受歡迎, 經(jīng)過(guò)同行鑒定, 免費(fèi)開(kāi)源的 C++ 庫(kù)集.
優(yōu)點(diǎn):
Boost代碼質(zhì)量普遍較高, 可移植性好, 填補(bǔ)了 C++ 標(biāo)準(zhǔn)庫(kù)很多空白, 如型別的特性, 更完善的綁定器, 更好的智能指針。
缺點(diǎn):
某些 Boost 庫(kù)提倡的編程實(shí)踐可讀性差, 比如元編程和其他高級(jí)模板技術(shù), 以及過(guò)度 “函數(shù)化” 的編程風(fēng)格.
結(jié)論:
為了向閱讀和維護(hù)代碼的人員提供更好的可讀性, 我們只允許使用 Boost 一部分經(jīng)認(rèn)可的特性子集. 目前允許使用以下庫(kù):
Call Traits:boost/call_traits.hpp
Compressed Pair:boost/compressed_pair.hpp
Property Map:boost/property_map.hpp
The part ofIteratorthat deals with defining iterators:boost/iterator/iterator_adaptor.hpp,boost/iterator/iterator_facade.hpp, andboost/function_output_iterator.hpp
The part ofPolygonthat deals with Voronoi diagram construction and doesn’t depend on the rest of Polygon:boost/polygon/voronoi_builder.hpp,boost/polygon/voronoi_diagram.hpp, andboost/polygon/voronoi_geometry_type.hpp
Bimap:boost/bimap
Statistical Distributions and Functions:boost/math/distributions
Multi-index:boost/multi_index
Heap:boost/heap
The flat containers fromContainer:boost/container/flat_map, andboost/container/flat_set
我們正在積極考慮增加其它 Boost 特性, 所以列表中的規(guī)則將不斷變化.
以下庫(kù)可以用,但由于如今已經(jīng)被 C++ 11 標(biāo)準(zhǔn)庫(kù)取代,不再鼓勵(lì):
Pointer Container:boost/ptr_container, 改用std::unique_ptr
Array:boost/array.hpp, 改用std::array
6.24. C++11
Tip
適當(dāng)用 C++11(前身是 C++0x)的庫(kù)和語(yǔ)言擴(kuò)展,在貴項(xiàng)目用 C++11 特性前三思可移植性。
定義:
C++11 有眾多語(yǔ)言和庫(kù)上的`變革
優(yōu)點(diǎn):
在二〇一四年八月之前,C++11 一度是官方標(biāo)準(zhǔn),被大多 C++ 編譯器支持。它標(biāo)準(zhǔn)化很多我們?cè)缦染驮谟玫?C++ 擴(kuò)展,簡(jiǎn)化了不少操作,大大改善了性能和安全。
缺點(diǎn):
C++11 相對(duì)于前身,復(fù)雜極了:1300 頁(yè) vs 800 頁(yè)!很多開(kāi)發(fā)者也不怎么熟悉它。于是從長(zhǎng)遠(yuǎn)來(lái)看,前者特性對(duì)代碼可讀性以及維護(hù)代價(jià)難以預(yù)估。我們說(shuō)不準(zhǔn)什么時(shí)候采納其特性,特別是在被迫依賴(lài)?yán)蠈?shí)工具的項(xiàng)目上。
和6.23. Boost 庫(kù)一樣,有些 C++11 擴(kuò)展提倡實(shí)則對(duì)可讀性有害的編程實(shí)踐——就像去除冗余檢查(比如類(lèi)型名)以幫助讀者,或是鼓勵(lì)模板元編程等等。有些擴(kuò)展在功能上與原有機(jī)制沖突,容易招致困惑以及遷移代價(jià)。
缺點(diǎn):
C++11 特性除了個(gè)別情況下,可以用一用。除了本指南會(huì)有不少章節(jié)會(huì)加以討若干 C++11 特性之外,以下特性最好不要用:
尾置返回類(lèi)型,比如用autofoo()->int代替intfoo(). 為了兼容于現(xiàn)有代碼的聲明風(fēng)格。
編譯時(shí)合數(shù)
默認(rèn) lambda 捕獲。
譯者(acgtyrant)筆記
實(shí)際上,缺省參數(shù)會(huì)改變函數(shù)簽名的前提是改變了它接收的參數(shù)數(shù)量,比如把voida()改成voida(intb=0), 開(kāi)發(fā)者改變其代碼的初衷也許是,在不改變「代碼兼容性」的同時(shí),又提供了可選 int 參數(shù)的余地,然而這終究會(huì)破壞函數(shù)指針上的兼容性,畢竟函數(shù)簽名確實(shí)變了。
此外把自帶缺省參數(shù)的函數(shù)地址賦值給指針時(shí),會(huì)丟失缺省參數(shù)信息。
我還發(fā)現(xiàn)濫用缺省參數(shù)會(huì)害得讀者光只看調(diào)用代碼的話(huà),會(huì)誤以為其函數(shù)接受的參數(shù)數(shù)量比實(shí)際上還要少。
friend實(shí)際上只對(duì)函數(shù)/類(lèi)賦予了對(duì)其所在類(lèi)的訪(fǎng)問(wèn)權(quán)限,并不是有效的聲明語(yǔ)句。所以除了在頭文件類(lèi)內(nèi)部寫(xiě) friend 函數(shù)/類(lèi),還要在類(lèi)作用域之外正式地聲明一遍,最后在對(duì)應(yīng)的.cc文件加以定義。
本風(fēng)格指南都強(qiáng)調(diào)了「友元應(yīng)該定義在同一文件內(nèi),避免代碼讀者跑到其它文件查找使用該私有成員的類(lèi)」。那么可以把其聲明放在類(lèi)聲明所在的頭文件,定義也放在類(lèi)定義所在的文件。
由于友元函數(shù)/類(lèi)并不是類(lèi)的一部分,自然也不會(huì)是類(lèi)可調(diào)用的公有接口,于是我主張全集中放在類(lèi)的尾部,即的數(shù)據(jù)成員之后,參考聲明順序。
對(duì)使用 C++ 異常處理應(yīng)具有怎樣的態(tài)度?非常值得一讀。
注意初始化 const 對(duì)象時(shí),必須在初始化的同時(shí)值初始化。
用斷言代替無(wú)符號(hào)整型類(lèi)型,深有啟發(fā)。
auto 在涉及迭代器的循環(huán)語(yǔ)句里挺常用。
Should the trailing return type syntax style become the default for new C++11 programs?討論了 auto 與尾置返回類(lèi)型一起用的全新編碼風(fēng)格,值得一看。
-
Google
+關(guān)注
關(guān)注
5文章
1782瀏覽量
58566 -
編程
+關(guān)注
關(guān)注
88文章
3674瀏覽量
94746 -
C++
+關(guān)注
關(guān)注
22文章
2116瀏覽量
74645
原文標(biāo)題:Google C++ 編程規(guī)范 - 4
文章出處:【微信號(hào):C_Expert,微信公眾號(hào):C語(yǔ)言專(zhuān)家集中營(yíng)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
MATLAB 編程風(fēng)格指南
Google C++編程指南
MATLAB編程風(fēng)格指南
Google編程風(fēng)格指南(一)
Google編程風(fēng)格指南(二)
Google編程風(fēng)格指南(三)
Google編程風(fēng)格指南(五)
Google編程風(fēng)格指南(六)
Google C++編程風(fēng)格指南PDF版免費(fèi)下載

Google C++編程風(fēng)格指南PDF電子書(shū)免費(fèi)下載

評(píng)論