5. 其他 C++ 特性

2018-02-24 15:11 更新

5.1. 引用參數(shù)

Tip

所以按引用傳遞的參數(shù)必須加上 const.

定義:在 C 語(yǔ)言中, 如果函數(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(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ù).

在以下情況你可以把輸入?yún)?shù)定義為 const 指針: 你想強(qiáng)調(diào)參數(shù)不是拷貝而來(lái)的, 在對(duì)象生存周期內(nèi)必須一直存在; 最好同時(shí)在注釋中詳細(xì)說(shuō)明一下. bind2ndmem_fun 等 STL 適配器不接受引用參數(shù), 這種情況下你也必須把函數(shù)參數(shù)聲明成指針類型.

5.2. 函數(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):通過(guò)重載參數(shù)不同的同名函數(shù), 令代碼更加直觀. 模板化代碼需要重載, 同時(shí)為使用者帶來(lái)便利.缺點(diǎn):限制使用重載的一個(gè)原因是在某個(gè)特定調(diào)用點(diǎn)很難確定到底調(diào)用的是哪個(gè)函數(shù). 另一個(gè)原因是當(dāng)派生類只重載了某個(gè)函數(shù)的部分變體, 會(huì)令很多人對(duì)繼承的語(yǔ)義產(chǎn)生困惑. 此外在閱讀庫(kù)的用戶代碼時(shí), 可能會(huì)因反對(duì)使用 缺省函數(shù)參數(shù) [http://code.google.com/p/google-gflags/] 造成不必要的費(fèi)解.結(jié)論:如果你想重載一個(gè)函數(shù), 考慮讓函數(shù)名包含參數(shù)信息, 例如, 使用 AppendString(), AppendInt() 而不是 Append().

5.3. 缺省參數(shù)

Tip

我們不允許使用缺省函數(shù)參數(shù).

優(yōu)點(diǎn):多數(shù)情況下, 你寫的函數(shù)可能會(huì)用到很多的缺省值, 但偶爾你也會(huì)修改這些缺省值. 無(wú)須為了這些偶爾情況定義很多的函數(shù), 用缺省參數(shù)就能很輕松的做到這點(diǎn).缺點(diǎn):大家通常都是通過(guò)查看別人的代碼來(lái)推斷如何使用 API. 用了缺省參數(shù)的代碼更難維護(hù), 從老代碼復(fù)制粘貼而來(lái)的新代碼可能只包含部分參數(shù). 當(dāng)缺省參數(shù)不適用于新代碼時(shí)可能會(huì)導(dǎo)致重大問題.結(jié)論:我們規(guī)定所有參數(shù)必須明確指定, 迫使程序員理解 API 和各參數(shù)值的意義, 避免默默使用他們可能都還沒意識(shí)到的缺省參數(shù).

5.4. 變長(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é)論:使用安全的內(nèi)存分配器, 如 scoped_ptr / scoped_array.

5.5. 友元

Tip

我們?cè)试S合理的使用友元類及友元函數(shù).

通常友元應(yīng)該定義在同一文件內(nèi), 避免代碼讀者跑到其它文件查找使用該私有成員的類. 經(jīng)常用到友元的一個(gè)地方是將 FooBuilder 聲明為 Foo 的友元, 以便 FooBuilder 正確構(gòu)造 Foo 的內(nèi)部狀態(tài), 而無(wú)需將該狀態(tài)暴露出來(lái). 某些情況下, 將一個(gè)單元測(cè)試類聲明成待測(cè)類的友元會(huì)很方便.

友元擴(kuò)大了 (但沒有打破) 類的封裝邊界. 某些情況下, 相對(duì)于將類成員聲明為 public, 使用友元是更好的選擇, 尤其是如果你只允許另一個(gè)類訪問該類的私有成員時(shí). 當(dāng)然, 大多數(shù)類都只應(yīng)該通過(guò)其提供的公有成員進(jìn)行互操作.

5.6. 異常

Tip

我們不使用 C++ 異常.

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

  • 異常允許上層應(yīng)用決定如何處理在底層嵌套函數(shù)中 “不可能出現(xiàn)的” 失敗, 不像錯(cuò)誤碼記錄那么含糊又易出錯(cuò);
  • 很多現(xiàn)代語(yǔ)言都使用異常. 引入異常使得 C++ 與 Python, Java 以及其它 C++ 相近的語(yǔ)言更加兼容.
  • 許多第三方 C++ 庫(kù)使用異常, 禁用異常將導(dǎo)致很難集成這些庫(kù).
  • 異常是處理構(gòu)造函數(shù)失敗的唯一方法. 雖然可以通過(guò)工廠函數(shù)或 Init() 方法替代異常, 但他們分別需要堆分配或新的 “無(wú)效” 狀態(tài);
  • 在測(cè)試框架中使用異常確實(shí)很方便.

缺點(diǎn):

  • 在現(xiàn)有函數(shù)中添加 throw 語(yǔ)句時(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)心了, 很可能會(huì)因疏忽而未被妥善清理.
  • 更普遍的情況是, 如果使用異常, 光憑查看代碼是很難評(píng)估程序的控制流: 函數(shù)返回點(diǎn)可能在你意料之外. 這回導(dǎo)致代碼管理和調(diào)試?yán)щy. 你可以通過(guò)規(guī)定何時(shí)何地如何使用異常來(lái)降低開銷, 但是讓開發(fā)人員必須掌握并理解這些規(guī)定帶來(lái)的代價(jià)更大.
  • 異常安全要求同時(shí)采用 RAII 和不同編程實(shí)踐. 要想輕松編寫正確的異常安全代碼, 需要大量的支撐機(jī)制配合. 另外, 要避免代碼讀者去理解整個(gè)調(diào)用結(jié)構(gòu)圖, 異常安全代碼必須把寫持久化狀態(tài)的邏輯部分隔離到 “提交” 階段. 它在帶來(lái)好處的同時(shí), 還有成本 (也許你不得不為了隔離 “提交” 而整出令人費(fèi)解的代碼). 允許使用異常會(huì)驅(qū)使我們不斷為此付出代價(jià), 即使我們覺得這很不劃算.
  • 啟用異常使生成的二進(jìn)制文件體積變大, 延長(zhǎng)了編譯時(shí)間 (或許影響不大), 還可能增加地址空間壓力;
  • 異常的實(shí)用性可能會(huì)慫恿開發(fā)人員在不恰當(dāng)?shù)臅r(shí)候拋出異常, 或者在不安全的地方從異常中恢復(fù). 例如, 處理非法用戶輸入時(shí)就不應(yīng)該拋出異常. 如果我們要完全列出這些約束, 這份風(fēng)格指南會(huì)長(zhǎng)出很多!

結(jié)論:
從表面上看, 使用異常利大于弊, 尤其是在新項(xiàng)目中. 但是對(duì)于現(xiàn)有代碼, 引入異常會(huì)牽連到所有相關(guān)代碼. 如果新項(xiàng)目允許異常向外擴(kuò)散, 在跟以前未使用異常的代碼整合時(shí)也將是個(gè)麻煩. 因?yàn)?Google 現(xiàn)有的大多數(shù) C++ 代碼都沒有異常處理, 引入帶有異常處理的新代碼相當(dāng)困難.

鑒于 Google 現(xiàn)有代碼不接受異常, 在現(xiàn)有代碼中使用異常比在新項(xiàng)目中使用的代價(jià)多少要大一些. 遷移過(guò)程比較慢, 也容易出錯(cuò). 我們不相信異常的使用有效替代方案, 如錯(cuò)誤代碼, 斷言等會(huì)造成嚴(yán)重負(fù)擔(dān).

我們并不是基于哲學(xué)或道德層面反對(duì)使用異常, 而是在實(shí)踐的基礎(chǔ)上. 我們希望在 Google 使用我們自己的開源項(xiàng)目, 但項(xiàng)目中使用異常會(huì)為此帶來(lái)不便, 因此我們也建議不要在 Google 的開源項(xiàng)目中使用異常. 如果我們需要把這些項(xiàng)目推倒重來(lái)顯然不太現(xiàn)實(shí).

對(duì)于 Windows 代碼來(lái)說(shuō), 有個(gè) 特例.

(YuleFox 注: 對(duì)于異常處理, 顯然不是短短幾句話能夠說(shuō)清楚的, 以構(gòu)造函數(shù)為例, 很多 C++ 書籍上都提到當(dāng)構(gòu)造失敗時(shí)只有異常可以處理, Google 禁止使用異常這一點(diǎn), 僅僅是為了自身的方便, 說(shuō)大了, 無(wú)非是基于軟件管理成本上, 實(shí)際使用中還是自己決定)

5.7. 運(yùn)行時(shí)類型識(shí)別

Tip

我們禁止使用 RTTI.

定義:RTTI 允許程序員在運(yùn)行時(shí)識(shí)別 C++ 類對(duì)象的類型.優(yōu)點(diǎn):
RTTI 在某些單元測(cè)試中非常有用. 比如進(jìn)行工廠類測(cè)試時(shí), 用來(lái)驗(yàn)證一個(gè)新建對(duì)象是否為期望的動(dòng)態(tài)類型.

除測(cè)試外, 極少用到.

缺點(diǎn):在運(yùn)行時(shí)判斷類型通常意味著設(shè)計(jì)問題. 如果你需要在運(yùn)行期間確定一個(gè)對(duì)象的類型, 這通常說(shuō)明你需要考慮重新設(shè)計(jì)你的類.結(jié)論:
除單元測(cè)試外, 不要使用 RTTI. 如果你發(fā)現(xiàn)自己不得不寫一些行為邏輯取決于對(duì)象類型的代碼, 考慮換一種方式判斷對(duì)象類型.

如果要實(shí)現(xiàn)根據(jù)子類類型來(lái)確定執(zhí)行不同邏輯代碼, 虛函數(shù)無(wú)疑更合適. 在對(duì)象內(nèi)部就可以處理類型識(shí)別問題.

如果要在對(duì)象外部的代碼中判斷類型, 考慮使用雙重分派方案, 如訪問者模式. 可以方便的在對(duì)象本身之外確定類的類型.

如果你認(rèn)為上面的方法你真的掌握不了, 你可以使用 RTTI, 但務(wù)必請(qǐng)三思 :-) . 不要試圖手工實(shí)現(xiàn)一個(gè)貌似 RTTI 的替代方案, 我們反對(duì)使用 RTTI 的理由, 同樣適用于那些在類型繼承體系上使用類型標(biāo)簽的替代方案.

5.8. 類型轉(zhuǎn)換

Tip

使用 C++ 的類型轉(zhuǎn)換, 如 static_cast<>(). 不要使用 int y = (int)xint y = int(x) 等轉(zhuǎn)換方式;

定義:C++ 采用了有別于 C 的類型轉(zhuǎn)換機(jī)制, 對(duì)轉(zhuǎn)換操作進(jìn)行歸類.優(yōu)點(diǎn):C 語(yǔ)言的類型轉(zhuǎn)換問題在于模棱兩可的操作; 有時(shí)是在做強(qiáng)制轉(zhuǎn)換 (如 (int)3.5), 有時(shí)是在做類型轉(zhuǎn)換 (如 (int)"hello"). 另外, C++ 的類型轉(zhuǎn)換在查找時(shí)更醒目.缺點(diǎn):惡心的語(yǔ)法.結(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)換. 僅在你對(duì)所做一切了然于心時(shí)使用.
  • dynamic_cast 測(cè)試代碼以外不要使用. 除非是單元測(cè)試, 如果你需要在運(yùn)行時(shí)確定類型信息, 說(shuō)明有 設(shè)計(jì)缺陷.

5.9. 流

Tip

只在記錄日志時(shí)使用流.

定義:流用來(lái)替代 printf()scanf().優(yōu)點(diǎn):有了流, 在打印時(shí)不需要關(guān)心對(duì)象的類型. 不用擔(dān)心格式化字符串與參數(shù)列表不匹配 (雖然在 gcc 中使用 printf 也不存在這個(gè)問題). 流的構(gòu)造和析構(gòu)函數(shù)會(huì)自動(dòng)打開和關(guān)閉對(duì)應(yīng)的文件.缺點(diǎn):流使得 pread() 等功能函數(shù)很難執(zhí)行. 如果不使用 printf 風(fēng)格的格式化字符串, 某些格式化操作 (尤其是常用的格式字符串 %.*s) 用流處理性能是很低的. 流不支持字符串操作符重新排序 (%1s), 而這一點(diǎn)對(duì)于軟件國(guó)際化很有用.結(jié)論:
不要使用流, 除非是日志接口需要. 使用 printf 之類的代替.

使用流還有很多利弊, 但代碼一致性勝過(guò)一切. 不要在代碼中使用流.

拓展討論:
對(duì)這一條規(guī)則存在一些爭(zhēng)論, 這兒給出點(diǎn)深層次原因. 回想一下唯一性原則 (Only One Way): 我們希望在任何時(shí)候都只使用一種確定的 I/O 類型, 使代碼在所有 I/O 處都保持一致. 因此, 我們不希望用戶來(lái)決定是使用流還是 printf + read/write. 相反, 我們應(yīng)該決定到底用哪一種方式. 把日志作為特例是因?yàn)槿罩臼且粋€(gè)非常獨(dú)特的應(yīng)用, 還有一些是歷史原因.

流的支持者們主張流是不二之選, 但觀點(diǎn)并不是那么清晰有力. 他們指出的流的每個(gè)優(yōu)勢(shì)也都是其劣勢(shì). 流最大的優(yōu)勢(shì)是在輸出時(shí)不需要關(guān)心打印對(duì)象的類型. 這是一個(gè)亮點(diǎn). 同時(shí), 也是一個(gè)不足: 你很容易用錯(cuò)類型, 而編譯器不會(huì)報(bào)警. 使用流時(shí)容易造成的這類錯(cuò)誤:

cout << this;   // Prints the address
cout << *this;  // Prints the contents

由于 << 被重載, 編譯器不會(huì)報(bào)錯(cuò). 就因?yàn)檫@一點(diǎn)我們反對(duì)使用操作符重載.

有人說(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í)的新裝備.

每一種方式都是各有利弊, “沒有最好, 只有更適合”. 簡(jiǎn)單性原則告誡我們必須從中選擇其一, 最后大多數(shù)決定采用 printf + read/write.

5.10. 前置自增和自減

Tip

對(duì)于迭代器和其他模板對(duì)象使用前綴形式 (++i) 的自增, 自減運(yùn)算符.

定義:對(duì)于變量在自增 (++ii++) 或自減 (--ii--) 后表達(dá)式的值又沒有沒用到的情況下, 需要確定到底是使用前置還是后置的自增 (自減).優(yōu)點(diǎn):不考慮返回值的話, 前置自增 (++i) 通常要比后置自增 (i++) 效率更高. 因?yàn)楹笾米栽?(或自減) 需要對(duì)表達(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)檫@很像自然語(yǔ)言, 主語(yǔ) (i) 在謂語(yǔ)動(dòng)詞 (++) 前.結(jié)論:對(duì)簡(jiǎn)單數(shù)值 (非對(duì)象), 兩種都無(wú)所謂. 對(duì)迭代器和模板類型, 使用前置自增 (自減).

5.11. const 的使用

Tip

我們強(qiáng)烈建議你在任何可能的情況下都要使用 const.

定義:在聲明的變量或參數(shù)前加上關(guān)鍵字 const 用于指明變量值不可被篡改 (如 const int foo ). 為類中的函數(shù)加上 const 限定符表明該函數(shù)不會(huì)修改類成員變量的狀態(tài) (如 class Foo { int Bar(char c) const; };).優(yōu)點(diǎn):大家更容易理解如何使用變量. 編譯器可以更好地進(jìn)行類型檢測(cè), 相應(yīng)地, 也能生成更好的代碼. 人們對(duì)編寫正確的代碼更加自信, 因?yàn)樗麄冎浪{(diào)用的函數(shù)被限定了能或不能修改變量值. 即使是在無(wú)鎖的多線程編程中, 人們也知道什么樣的函數(shù)是安全的.缺點(diǎn):const 是入侵性的: 如果你向一個(gè)函數(shù)傳入 const 變量, 函數(shù)原型聲明中也必須對(duì)應(yīng) const 參數(shù) (否則變量需要 const_cast 類型轉(zhuǎn)換), 在調(diào)用庫(kù)函數(shù)時(shí)顯得尤其麻煩.結(jié)論:
const 變量, 數(shù)據(jù)成員, 函數(shù)和參數(shù)為編譯時(shí)類型檢測(cè)增加了一層保障; 便于盡早發(fā)現(xiàn)錯(cuò)誤. 因此, 我們強(qiáng)烈建議在任何可能的情況下使用 const:

  • 如果函數(shù)不會(huì)修改傳入的引用或指針類型參數(shù), 該參數(shù)應(yīng)聲明為 const.
  • 盡可能將函數(shù)聲明為 const. 訪問函數(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. 像 const int * const * const x; 就有些過(guò)了, 雖然它非常精確的描述了常量 x. 關(guān)注真正有幫助意義的信息: 前面的例子寫成 const int** x 就夠了.

關(guān)鍵字 mutable 可以使用, 但是在多線程中是不安全的, 使用時(shí)首先要考慮線程安全.

const 的位置:
有人喜歡 int const *foo 形式, 不喜歡 const int* foo, 他們認(rèn)為前者更一致因此可讀性也更好: 遵循了 const 總位于其描述的對(duì)象之后的原則. 但是一致性原則不適用于此, “不要過(guò)度使用” 的聲明可以取消大部分你原本想保持的一致性. 將 const 放在前面才更易讀, 因?yàn)樵谧匀徽Z(yǔ)言中形容詞 (const) 是在名詞 (int) 之前.

這是說(shuō), 我們提倡但不強(qiáng)制 const 在前. 但要保持代碼的一致性! (yospaly 注: 也就是不要在一些地方把 const 寫在類型前面, 在其他地方又寫在后面, 確定一種寫法, 然后保持一致.)

5.12. 整型

Tip

C++ 內(nèi)建整型中, 僅使用 int. 如果程序中需要不同大小的變量, 可以使用 <stdint.h> 中長(zhǎng)度精確的整型, 如 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_tptrdiff_t.

如果已知整數(shù)不會(huì)太大, 我們常常會(huì)使用 int, 如循環(huán)計(jì)數(shù). 在類似的情況下使用原生類型 int. 你可以認(rèn)為 int 至少為 32 位, 但不要認(rèn)為它會(huì)多于 32 位. 如果需要 64 位整型, 用 int64_tuint64_t.

對(duì)于大整數(shù), 使用 int64_t.

不要使用 uint32_t 等無(wú)符號(hào)整型, 除非你是在表示一個(gè)位組而不是一個(gè)數(shù)值, 或是你需要定義二進(jìn)制補(bǔ)碼溢出. 尤其是不要為了指出數(shù)值永不會(huì)為負(fù), 而使用無(wú)符號(hào)類型. 相反, 你應(yīng)該使用斷言來(lái)保護(hù)數(shù)據(jù).

關(guān)于無(wú)符號(hào)整數(shù):有些人, 包括一些教科書作者, 推薦使用無(wú)符號(hào)類型表示非負(fù)數(shù). 這種做法試圖達(dá)到自我文檔化. 但是, 在 C 語(yǔ)言中, 這一優(yōu)點(diǎn)被由其導(dǎo)致的 bug 所淹沒. 看看下面的例子:

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ì). 類似的 bug 還會(huì)出現(xiàn)在比較有符合變量和無(wú)符號(hào)變量時(shí). 主要是 C 的類型提升機(jī)制會(huì)致使無(wú)符號(hào)類型的行為出乎你的意料.

因此, 使用斷言來(lái)指出變量為非負(fù)數(shù), 而不是使用無(wú)符號(hào)型!

5.13. 64 位下的可移植性

Tip

代碼應(yīng)該對(duì) 64 位和 32 位系統(tǒng)友好. 處理打印, 比較, 結(jié)構(gòu)體對(duì)齊時(shí)應(yīng)切記:

  • 對(duì)于某些類型, 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* 宏會(huì)被編譯器擴(kuò)展為獨(dú)立字符串. 因此如果使用非常量的格式化字符串, 需要將宏的值而不是宏名插入格式中. 使用 PRI* 宏同樣可以在 % 后包含長(zhǎng)度指示符. 例如, 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 編譯器不會(huì)自動(dòng)把引號(hào)間隔的多個(gè)字符串連接一個(gè)長(zhǎng)字符串).

  • 記住 sizeof(void *) != sizeof(int). 如果需要一個(gè)指針大小的整數(shù)要用 intptr_t.

  • 你要非常小心的對(duì)待結(jié)構(gòu)體對(duì)齊, 尤其是要持久化到磁盤上的結(jié)構(gòu)體 (yospaly 注: 持久化 - 將數(shù)據(jù)按字節(jié)流順序保存在磁盤文件或數(shù)據(jù)庫(kù)中). 在 64 位系統(tǒng)中, 任何含有 int64_t/uint64_t 成員的類/結(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 則提供了 #pragma pack()__declspec(align()) (YuleFox 注, 解決方案的項(xiàng)目屬性里也可以直接設(shè)置).

  • 創(chuàng)建 64 位常量時(shí)使用 LL 或 ULL 作為后綴, 如:
int64_t my_value = 0×123456789LL;
uint64_t my_mask = 3ULL << 48;

如果你確實(shí)需要 32 位和 64 位系統(tǒng)具有不同代碼, 可以使用 #ifdef _LP64 指令來(lái)切分 32/64 位代碼. (盡量不要這么做, 如果非用不可, 盡量使修改局部化)

5.14. 預(yù)處理宏

Tip

使用宏時(shí)要非常謹(jǐn)慎, 盡量以內(nèi)聯(lián)函數(shù), 枚舉和常量代替之.

宏意味著你和編譯器看到的代碼是不同的. 這可能會(huì)導(dǎo)致異常行為, 尤其因?yàn)楹昃哂腥肿饔糜?

值得慶幸的是, C++ 中, 宏不像在 C 中那么必不可少. 以往用宏展開性能關(guān)鍵的代碼, 現(xiàn)在可以用內(nèi)聯(lián)函數(shù)替代. 用宏表示常量可被?const?變量代替. 用宏 “縮寫” 長(zhǎng)變量名可被引用代替. 用宏進(jìn)行條件編譯... 這個(gè), 千萬(wàn)別這么做, 會(huì)令測(cè)試更加痛苦 (#define?防止頭文件重包含當(dāng)然是個(gè)特例).

宏可以做一些其他技術(shù)無(wú)法實(shí)現(xiàn)的事情, 在一些代碼庫(kù) (尤其是底層庫(kù)中) 可以看到宏的某些特性 (如用?#?字符串化, 用?##?連接等等). 但在使用前, 仔細(xì)考慮一下能不能不使用宏達(dá)到同樣的目的.

下面給出的用法模式可以避免使用宏帶來(lái)的問題; 如果你要宏, 盡可能遵守:

  • 不要在?.h?文件中定義宏.
  • 在馬上要使用時(shí)才進(jìn)行?#define, 使用后要立即?#undef.
  • 不要只是對(duì)已經(jīng)存在的宏使用#undef,選擇一個(gè)不會(huì)沖突的名稱;
  • 不要試圖使用展開后會(huì)導(dǎo)致 C++ 構(gòu)造不穩(wěn)定的宏, 不然也至少要附上文檔說(shuō)明其行為.

5.15. 0 和 NULL

Tip

整數(shù)用?0, 實(shí)數(shù)用?0.0, 指針用?NULL, 字符 (串) 用?'\0'.

整數(shù)用?0, 實(shí)數(shù)用?0.0, 這一點(diǎn)是毫無(wú)爭(zhēng)議的.

對(duì)于指針 (地址值), 到底是用?0?還是?NULL, Bjarne Stroustrup 建議使用最原始的?0. 我們建議使用看上去像是指針的?NULL, 事實(shí)上一些 C++ 編譯器 (如 gcc 4.1.0) 對(duì)?NULL?進(jìn)行了特殊的定義, 可以給出有用的警告信息, 尤其是?sizeof(NULL)?和?sizeof(0)?不相等的情況.

字符 (串) 用?'\0', 不僅類型正確而且可讀性好.

5.16. sizeof

Tip

盡可能用?sizeof(varname)?代替?sizeof(type).

使用?sizeof(varname)?是因?yàn)楫?dāng)代碼中變量類型改變時(shí)會(huì)自動(dòng)更新. 某些情況下?sizeof(type)?或許有意義, 但還是要盡量避免, 因?yàn)樗鼤?huì)導(dǎo)致變量類型改變后不能同步.

Struct data;
Struct data; memset(&data, 0, sizeof(data));

Warning

memset(&data, 0, sizeof(Struct));

5.17. Boost 庫(kù)

Tip

只使用 Boost 中被認(rèn)可的庫(kù).

定義:

Boost 庫(kù)集?是一個(gè)廣受歡迎, 經(jīng)過(guò)同行鑒定, 免費(fèi)開源的 C++ 庫(kù)集.

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

Boost代碼質(zhì)量普遍較高, 可移植性好, 填補(bǔ)了 C++ 標(biāo)準(zhǔn)庫(kù)很多空白, 如型別的特性, 更完善的綁定器, 更好的智能指針, 同時(shí)還提供了?TR1?(標(biāo)準(zhǔn)庫(kù)擴(kuò)展) 的實(shí)現(xiàn).

缺點(diǎn):

某些 Boost 庫(kù)提倡的編程實(shí)踐可讀性差, 比如元編程和其他高級(jí)模板技術(shù), 以及過(guò)度 “函數(shù)化” 的編程風(fēng)格.

結(jié)論:

為了向閱讀和維護(hù)代碼的人員提供更好的可讀性, 我們只允許使用 Boost 一部分經(jīng)認(rèn)可的特性子集. 目前允許使用以下庫(kù):

我們正在積極考慮增加其它 Boost 特性, 所以列表中的規(guī)則將不斷變化.

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)