, 則 auto v2(std::move(v1)) 將很可能不再進(jìn)行大量的數(shù)據(jù)復(fù)制而只是簡(jiǎn)單地進(jìn)行指針操作, 在某些情況下這將帶來(lái)大幅度的性能提升." />

曰本美女∴一区二区特级A级黄色大片, 国产亚洲精品美女久久久久久2025, 页岩实心砖-高密市宏伟建材有限公司, 午夜小视频在线观看欧美日韩手机在线,国产人妻奶水一区二区,国产玉足,妺妺窝人体色WWW网站孕妇,色综合天天综合网中文伊,成人在线麻豆网观看

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線(xiàn)課程
  • 觀(guān)看技術(shù)視頻
  • 寫(xiě)文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

Google編程風(fēng)格指南(四)

C語(yǔ)言專(zhuān)家集中營(yíng) ? 來(lái)源:未知 ? 作者:李倩 ? 2018-09-27 18:08 ? 次閱讀

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, 則autov2(std::move(v1))將很可能不再進(jìn)行大量的數(shù)據(jù)復(fù)制而只是簡(jiǎn)單地進(jìn)行指針操作, 在某些情況下這將帶來(lái)大幅度的性能提升.

右值引用使得編寫(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(other); if (that == NULL) return false; ...}

缺點(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. 如果程序中需要不同大小的變量, 可以使用中長(zhǎng)度精確的整型, 如int16_t.如果您的變量可能不小于 2^31 (2GiB), 就用 64 位變量比如int64_t. 此外要留意,哪怕您的值并不會(huì)超出 int 所能夠表示的范圍,在計(jì)算過(guò)程中也可能會(huì)溢出。所以拿不準(zhǔn)時(shí),干脆用更大的類(lèi)型。

定義:

C++ 沒(méi)有指定整型的大小. 通常人們假定short是 16 位,int是 32 位,long是 32 位,longlong是 64 位.

優(yōu)點(diǎn):

保持聲明統(tǒng)一.

缺點(diǎn):

C++ 中整型大小因編譯器和體系結(jié)構(gòu)的不同而不同.

結(jié)論:

定義了int16_t,uint32_t,int64_t等整型, 在需要確保整型大小時(shí)可以使用它們代替short,unsignedlonglong等. 在 C 整型中, 只使用int. 在合適的情況下, 推薦使用標(biāo)準(zhǔn)類(lèi)型如size_t和ptrdiff_t.

如果已知整數(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 v;...auto s1 = v[0]; // 創(chuàng)建一份 v[0] 的拷貝。const auto& s2 = v[0]; // s2 是 v[0] 的一個(gè)引用。

優(yōu)點(diǎn):

C++ 類(lèi)型名有時(shí)又長(zhǎng)又臭,特別是涉及模板或命名空間的時(shí)候。就像:

sparse_hash_map::iterator iter = m.find(val);

返回類(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. 其它一般不可見(jiàn)的代理類(lèi)型(acgtyrant 注:normally-invisible proxy types, 它涉及到 C++ 鮮為人知的坑:Why is vector not a STL container?)也有大同小異的陷阱。

如果在接口里用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 v{"foo", "bar"};// 不考慮細(xì)節(jié)上的微妙差別,大致上相同。// 您可以任選其一。vector v = {"foo", "bar"};// 可以配合 new 一起用。auto p = new vector{"foo", "bar"};// map 接收了一些 pair, 列表初始化大顯神威。map m = {{1, "one"}, {2, "2"}};// 初始化列表也可以用在返回類(lèi)型上的隱式轉(zhuǎn)換。vector test_function() { return {1, 2, 3}; }// 初始化列表可迭代。for (int i : {-1, -2, -3}) {}// 在函數(shù)調(diào)用里用列表初始化。void TestFunction2(vector v) {}TestFunction2({1, 2, 3});

用戶(hù)自定義類(lèi)型也可以定義接收std::initializer_list的構(gòu)造函數(shù)和賦值運(yùn)算符,以自動(dòng)列表初始化:

class MyType { public: // std::initializer_list 專(zhuān)門(mén)接收 init 列表。 // 得以值傳遞。 MyType(std::initializer_list init_list) { for (int i : init_list) append(i); } MyType& operator=(std::initializer_list init_list) { clear(); for (int i : init_list) append(i); }};MyType m{2, 3, 5, 7};

最后,列表初始化也適用于常規(guī)數(shù)據(jù)類(lèi)型的構(gòu)造,哪怕沒(méi)有接收std::initializer_list的構(gòu)造函數(shù)。

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_listauto d = double{1.23}; // 善哉 -- d 即為 double, 并非 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ù), 因?yàn)樗婕耙粋€(gè)重模板的接口風(fēng)格。

頭文件,因?yàn)榫幾g器尚不支持。

默認(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)格,值得一看。

聲明:本文內(nèi)容及配圖由入駐作者撰寫(xiě)或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀(guān)點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • Google
    +關(guān)注

    關(guān)注

    5

    文章

    1782

    瀏覽量

    58566
  • 編程
    +關(guān)注

    關(guān)注

    88

    文章

    3674

    瀏覽量

    94746
  • C++
    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)注明出處。

收藏 人收藏

    評(píng)論

    相關(guān)推薦
    熱點(diǎn)推薦

    JAVA編程實(shí)例:多種風(fēng)格的窗口

    JAVA編程實(shí)例:多種風(fēng)格的窗口 
    發(fā)表于 12-06 12:37

    MATLAB 編程風(fēng)格指南

    thebeginning.”(良好的寫(xiě)作規(guī)范的程序比糟糕的寫(xiě)作規(guī)范的要好,因?yàn)樗麄兙哂休^少的錯(cuò)誤、易于調(diào)試與修改,因此,從一開(kāi)始就考慮風(fēng)格是很重要的)。本指南列舉的MATLAB 代碼編寫(xiě)的建議在
    發(fā)表于 09-22 16:19

    Google C++編程指南

    Google C++編程指南目標(biāo):增強(qiáng)代碼一致性,創(chuàng)建通用的、必需的習(xí)慣用語(yǔ)和模式可以使代碼更加容易理解C++是一門(mén)包含大量高級(jí)特性的巨型語(yǔ)言,某些情況下,我們會(huì)限制甚至禁止使用某些特性使代碼簡(jiǎn)化
    發(fā)表于 11-29 09:15

    MATLAB編程風(fēng)格指南

    有關(guān) MATLAB代碼的建議通常強(qiáng)調(diào)的是效率,譬如說(shuō)有關(guān)“不要用循環(huán)”等的建議,本指南與之不同。本指南主要考慮的是代碼(格式)的正確性、清晰性與通用性。本指南的目的在
    發(fā)表于 07-18 10:54 ?0次下載

    linux內(nèi)核C語(yǔ)言的編程風(fēng)格

    linux 內(nèi)核C語(yǔ)言的編程風(fēng)格
    發(fā)表于 09-26 14:22 ?0次下載

    Google編程風(fēng)格指南(一)

    使代碼易于管理的方法之一是加強(qiáng)代碼一致性. 讓任何程序員都可以快速讀懂你的代碼這點(diǎn)非常重要. 保持統(tǒng)一編程風(fēng)格并遵守約定意味著可以很容易根據(jù) “模式匹配” 規(guī)則來(lái)推斷各種標(biāo)識(shí)符的含義. 創(chuàng)建通用
    的頭像 發(fā)表于 09-27 17:57 ?3255次閱讀

    Google編程風(fēng)格指南(二)

    鼓勵(lì)在 .cc 文件內(nèi)使用匿名命名空間或 static 聲明. 使用具名的命名空間時(shí), 其名稱(chēng)可基于項(xiàng)目名或相對(duì)路徑. 禁止使用 using 指示(using-directive)。禁止使用內(nèi)聯(lián)命名空間(inline namespace)。
    的頭像 發(fā)表于 09-27 18:01 ?2719次閱讀

    Google編程風(fēng)格指南(三)

    C/C++ 中的函數(shù)參數(shù)或者是函數(shù)的輸入, 或者是函數(shù)的輸出, 或兼而有之. 輸入?yún)?shù)通常是值參或 const 引用, 輸出參數(shù)或輸入/輸出參數(shù)則一般為非 const 指針. 在排列參數(shù)順序時(shí), 將所有的輸入?yún)?shù)置于輸出參數(shù)之前. 特別要注意, 在加入新參數(shù)時(shí)不要因?yàn)樗鼈兪切聟?shù)就置于參數(shù)列表最后, 而是仍然要按照前述的規(guī)則, 即將新的輸入?yún)?shù)也置于輸出參數(shù)之前.
    的頭像 發(fā)表于 09-27 18:06 ?2689次閱讀

    Google編程風(fēng)格指南(五)

    所有具有靜態(tài)存儲(chǔ)類(lèi)型的變量 (例如靜態(tài)變量或全局變量, 參見(jiàn) 存儲(chǔ)類(lèi)型) 都應(yīng)當(dāng)以此方式命名. 對(duì)于其他存儲(chǔ)類(lèi)型的變量, 如自動(dòng)變量等, 這條規(guī)則是可選的. 如果不采用這條規(guī)則, 就按照一般的變量命名規(guī)則.
    的頭像 發(fā)表于 09-27 18:15 ?2628次閱讀

    Google編程風(fēng)格指南(六)

    即使是英文, 也不應(yīng)將用戶(hù)界面的文本硬編碼到源代碼中, 因此非 ASCII 字符應(yīng)當(dāng)很少被用到. 特殊情況下可以適當(dāng)包含此類(lèi)字符. 例如, 代碼分析外部數(shù)據(jù)文件時(shí), 可以適當(dāng)硬編碼數(shù)據(jù)文件中作為分隔符的非 ASCII 字符串; 更常見(jiàn)的是 (不需要本地化的) 單元測(cè)試代碼可能包含非 ASCII 字符串. 此類(lèi)情況下, 應(yīng)使用 UTF-8 編碼, 因?yàn)楹芏喙ぞ叨伎梢岳斫夂吞幚?UTF-8 編碼.
    的頭像 發(fā)表于 09-27 18:18 ?2885次閱讀

    Google C++編程風(fēng)格指南PDF版免費(fèi)下載

    Google的項(xiàng)目大多使用C++開(kāi)發(fā)。每一個(gè)C++程序員也都知道, C++具有很多強(qiáng)大的語(yǔ)言特性,但這種強(qiáng)大不可避免的導(dǎo)致它的復(fù)雜,而復(fù)雜性會(huì)使得代碼更容易出現(xiàn)bug.難于閱讀和維護(hù)。
    發(fā)表于 03-06 08:00 ?0次下載
    <b class='flag-5'>Google</b> C++<b class='flag-5'>編程</b><b class='flag-5'>風(fēng)格</b><b class='flag-5'>指南</b>PDF版免費(fèi)下載

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

    Google 的開(kāi)源項(xiàng)目大多使用 C++開(kāi)發(fā)。每一個(gè) C++程序員也都知道,C++具有很多強(qiáng)大的語(yǔ)言特性,但這種強(qiáng)大不可避免的導(dǎo)致它的復(fù)雜,這種復(fù)雜會(huì)使得代碼更易于出現(xiàn) bug、難于閱讀和維護(hù)。本
    發(fā)表于 12-12 08:00 ?1次下載
    <b class='flag-5'>Google</b> C++<b class='flag-5'>編程</b><b class='flag-5'>風(fēng)格</b><b class='flag-5'>指南</b>PDF電子書(shū)免費(fèi)下載

    Verilog HIDL的RTL設(shè)計(jì)風(fēng)格指南資源下載

    Verilog HIDL的RTL設(shè)計(jì)風(fēng)格指南資源下載
    發(fā)表于 04-13 10:09 ?9次下載

    西門(mén)子S7-1200和S7-1500編程風(fēng)格指南

    西門(mén)子S7-1200和S7-1500編程風(fēng)格指南分享
    發(fā)表于 08-17 17:30 ?22次下載

    Google Python代碼風(fēng)格指南

    1 背景 Python是谷歌主要使用的動(dòng)態(tài)語(yǔ)言,本風(fēng)格指導(dǎo)列舉了使用Python編程時(shí)應(yīng)該做和不該做的事項(xiàng)(dos nothing on first line # 縮進(jìn)4個(gè)空格,首行括號(hào)后無(wú)內(nèi)容
    的頭像 發(fā)表于 11-03 10:20 ?3292次閱讀