1. 頭文件

2018-02-24 15:11 更新

通常每一個(gè) .cc 文件都有一個(gè)對(duì)應(yīng)的 .h 文件. 也有一些常見例外, 如單元測(cè)試代碼和只包含 main() 函數(shù)的 .cc 文件.

正確使用頭文件可令代碼在可讀性、文件大小和性能上大為改觀.

下面的規(guī)則將引導(dǎo)你規(guī)避使用頭文件時(shí)的各種陷阱.

1.1. #define 保護(hù)

Tip

所有頭文件都應(yīng)該使用 #define 防止頭文件被多重包含, 命名格式當(dāng)是: <PROJECT>_<PATH>_<FILE>_H_

為保證唯一性, 頭文件的命名應(yīng)該依據(jù)所在項(xiàng)目源代碼樹的全路徑. 例如, 項(xiàng)目 foo 中的頭文件 foo/src/bar/baz.h 可按如下方式保護(hù):

#ifndef FOO_BAR_BAZ_H_
#define FOO_BAR_BAZ_H_
…
#endif // FOO_BAR_BAZ_H_

1.2. 頭文件依賴

Tip
能用前置聲明的地方盡量不使用 #include.

當(dāng)一個(gè)頭文件被包含的同時(shí)也引入了新的依賴, 一旦該頭文件被修改, 代碼就會(huì)被重新編譯. 如果這個(gè)頭文件又包含了其他頭文件, 這些頭文件的任何改變都將導(dǎo)致所有包含了該頭文件的代碼被重新編譯. 因此, 我們傾向于減少包含頭文件, 尤其是在頭文件中包含頭文件.

使用前置聲明可以顯著減少需要包含的頭文件數(shù)量. 舉例說明: 如果頭文件中用到類?File, 但不需要訪問?File?類的聲明, 頭文件中只需前置聲明?class?File;?而無須?#include"file/base/file.h".

不允許訪問類的定義的前提下, 我們?cè)谝粋€(gè)頭文件中能對(duì)類?Foo?做哪些操作?

  • 我們可以將數(shù)據(jù)成員類型聲明為?Foo?*?或?Foo?&.
  • 我們可以將函數(shù)參數(shù) / 返回值的類型聲明為?Foo?(但不能定義實(shí)現(xiàn)).
  • 我們可以將靜態(tài)數(shù)據(jù)成員的類型聲明為?Foo, 因?yàn)殪o態(tài)數(shù)據(jù)成員的定義在類定義之外.

反之, 如果你的類是?Foo?的子類, 或者含有類型為?Foo?的非靜態(tài)數(shù)據(jù)成員, 則必須包含?Foo?所在的頭文件.

有時(shí), 使用指針成員 (如果是?scoped_ptr?更好) 替代對(duì)象成員的確是明智之選. 然而, 這會(huì)降低代碼可讀性及執(zhí)行效率, 因此如果僅僅為了少包含頭文件,還是不要這么做的好.

當(dāng)然?.cc?文件無論如何都需要所使用類的定義部分, 自然也就會(huì)包含若干頭文件.

1.3. 內(nèi)聯(lián)函數(shù)

Tip

只有當(dāng)函數(shù)只有 10 行甚至更少時(shí)才將其定義為內(nèi)聯(lián)函數(shù).

定義:

當(dāng)函數(shù)被聲明為內(nèi)聯(lián)函數(shù)之后, 編譯器會(huì)將其內(nèi)聯(lián)展開, 而不是按通常的函數(shù)調(diào)用機(jī)制進(jìn)行調(diào)用.

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

當(dāng)函數(shù)體比較小的時(shí)候, 內(nèi)聯(lián)該函數(shù)可以令目標(biāo)代碼更加高效. 對(duì)于存取函數(shù)以及其它函數(shù)體比較短, 性能關(guān)鍵的函數(shù), 鼓勵(lì)使用內(nèi)聯(lián).

缺點(diǎn):

濫用內(nèi)聯(lián)將導(dǎo)致程序變慢. 內(nèi)聯(lián)可能使目標(biāo)代碼量或增或減, 這取決于內(nèi)聯(lián)函數(shù)的大小. 內(nèi)聯(lián)非常短小的存取函數(shù)通常會(huì)減少代碼大小, 但內(nèi)聯(lián)一個(gè)相當(dāng)大的函數(shù)將戲劇性的增加代碼大小. 現(xiàn)代處理器由于更好的利用了指令緩存, 小巧的代碼往往執(zhí)行更快。

結(jié)論:

一個(gè)較為合理的經(jīng)驗(yàn)準(zhǔn)則是, 不要內(nèi)聯(lián)超過 10 行的函數(shù). 謹(jǐn)慎對(duì)待析構(gòu)函數(shù), 析構(gòu)函數(shù)往往比其表面看起來要更長(zhǎng), 因?yàn)橛须[含的成員和基類析構(gòu)函數(shù)被調(diào)用!

另一個(gè)實(shí)用的經(jīng)驗(yàn)準(zhǔn)則: 內(nèi)聯(lián)那些包含循環(huán)或?switch?語(yǔ)句的函數(shù)常常是得不償失 (除非在大多數(shù)情況下, 這些循環(huán)或?switch?語(yǔ)句從不被執(zhí)行).

有些函數(shù)即使聲明為內(nèi)聯(lián)的也不一定會(huì)被編譯器內(nèi)聯(lián), 這點(diǎn)很重要; 比如虛函數(shù)和遞歸函數(shù)就不會(huì)被正常內(nèi)聯(lián). 通常, 遞歸函數(shù)不應(yīng)該聲明成內(nèi)聯(lián)函數(shù).(YuleFox 注: 遞歸調(diào)用堆棧的展開并不像循環(huán)那么簡(jiǎn)單, 比如遞歸層數(shù)在編譯時(shí)可能是未知的, 大多數(shù)編譯器都不支持內(nèi)聯(lián)遞歸函數(shù)). 虛函數(shù)內(nèi)聯(lián)的主要原因則是想把它的函數(shù)體放在類定義內(nèi), 為了圖個(gè)方便, 抑或是當(dāng)作文檔描述其行為, 比如精短的存取函數(shù).

1.4. -inl.h文件

Tip

復(fù)雜的內(nèi)聯(lián)函數(shù)的定義, 應(yīng)放在后綴名為?-inl.h?的頭文件中.

內(nèi)聯(lián)函數(shù)的定義必須放在頭文件中, 編譯器才能在調(diào)用點(diǎn)內(nèi)聯(lián)展開定義. 然而, 實(shí)現(xiàn)代碼理論上應(yīng)該放在?.cc?文件中, 我們不希望?.h?文件中有太多實(shí)現(xiàn)代碼, 除非在可讀性和性能上有明顯優(yōu)勢(shì).

如果內(nèi)聯(lián)函數(shù)的定義比較短小, 邏輯比較簡(jiǎn)單, 實(shí)現(xiàn)代碼放在?.h?文件里沒有任何問題. 比如, 存取函數(shù)的實(shí)現(xiàn)理所當(dāng)然都應(yīng)該放在類定義內(nèi). 出于編寫者和調(diào)用者的方便, 較復(fù)雜的內(nèi)聯(lián)函數(shù)也可以放到?.h?文件中, 如果你覺得這樣會(huì)使頭文件顯得笨重, 也可以把它萃取到單獨(dú)的?-inl.h?中. 這樣把實(shí)現(xiàn)和類定義分離開來, 當(dāng)需要時(shí)包含對(duì)應(yīng)的?-inl.h?即可。

-inl.h?文件還可用于函數(shù)模板的定義. 從而增強(qiáng)模板定義的可讀性.

別忘了?-inl.h?和其他頭文件一樣, 也需要?#define?保護(hù).

1.5. 函數(shù)參數(shù)的順序

Tip

定義函數(shù)時(shí), 參數(shù)順序依次為: 輸入?yún)?shù), 然后是輸出參數(shù).

C/C++ 函數(shù)參數(shù)分為輸入?yún)?shù), 輸出參數(shù), 和輸入/輸出參數(shù)三種. 輸入?yún)?shù)一般傳值或傳?const?引用, 輸出參數(shù)或輸入/輸出參數(shù)則是非-const?指針. 對(duì)參數(shù)排序時(shí), 將只輸入的參數(shù)放在所有輸出參數(shù)之前. 尤其是不要僅僅因?yàn)槭切录拥膮?shù), 就把它放在最后; 即使是新加的只輸入?yún)?shù)也要放在輸出參數(shù)之前.

這條規(guī)則并不需要嚴(yán)格遵守. 輸入/輸出兩用參數(shù) (通常是類/結(jié)構(gòu)體變量) 把事情變得復(fù)雜, 為保持和相關(guān)函數(shù)的一致性, 你有時(shí)不得不有所變通.

1.6.?#include?的路徑及順序

Tip

使用標(biāo)準(zhǔn)的頭文件包含順序可增強(qiáng)可讀性, 避免隱藏依賴: C 庫(kù), C++ 庫(kù), 其他庫(kù)的?.h, 本項(xiàng)目?jī)?nèi)的?.h.

項(xiàng)目?jī)?nèi)頭文件應(yīng)按照項(xiàng)目源代碼目錄樹結(jié)構(gòu)排列, 避免使用 UNIX 特殊的快捷目錄?.?(當(dāng)前目錄) 或?..?(上級(jí)目錄). 例如,?google-awesome-project/src/base/logging.h?應(yīng)該按如下方式包含:

#include "base/logging.h"

又如,?dir/foo.cc?的主要作用是實(shí)現(xiàn)或測(cè)試?dir2/foo2.h?的功能,?foo.cc?中包含頭文件的次序如下:

  1. dir2/foo2.h?(優(yōu)先位置, 詳情如下)
  2. C 系統(tǒng)文件
  3. C++ 系統(tǒng)文件
  4. 其他庫(kù)的?.h?文件
  5. 本項(xiàng)目?jī)?nèi)?.h?文件

這種排序方式可有效減少隱藏依賴. 我們希望每一個(gè)頭文件都是可被獨(dú)立編譯的 (yospaly 譯注: 即該頭文件本身已包含所有必要的顯式依賴), 最簡(jiǎn)單的方法是將其作為第一個(gè)?.h?文件?#included?進(jìn)對(duì)應(yīng)的?.cc.

dir/foo.cc?和?dir2/foo2.h?通常位于同一目錄下 (如?base/basictypes_unittest.cc?和?base/basictypes.h), 但也可以放在不同目錄下.

按字母順序?qū)︻^文件包含進(jìn)行二次排序是不錯(cuò)的主意 (yospaly 譯注: 之前已經(jīng)按頭文件類別排過序了).

舉例來說,?google-awesome-project/src/foo/internal/fooserver.cc?的包含次序如下:

#include "foo/public/fooserver.h" // 優(yōu)先位置
#include 
#include 
#include 
#include 
#include "base/basictypes.h"
#include "base/commandlineflags.h"
#include "foo/public/bar.h"

譯者 (YuleFox) 筆記

  1. 避免多重包含是學(xué)編程時(shí)最基本的要求;
  2. 前置聲明是為了降低編譯依賴,防止修改一個(gè)頭文件引發(fā)多米諾效應(yīng);
  3. 內(nèi)聯(lián)函數(shù)的合理使用可提高代碼執(zhí)行效率;
  4. -inl.h?可提高代碼可讀性 (一般用不到吧:D);
  5. 標(biāo)準(zhǔn)化函數(shù)參數(shù)順序可以提高可讀性和易維護(hù)性 (對(duì)函數(shù)參數(shù)的堆??臻g有輕微影響, 我以前大多是相同類型放在一起);
  6. 包含文件的名稱使用?.?和?..?雖然方便卻易混亂, 使用比較完整的項(xiàng)目路徑看上去很清晰, 很條理, 包含文件的次序除了美觀之外, 最重要的是可以減少隱藏依賴, 使每個(gè)頭文件在 “最需要編譯” (對(duì)應(yīng)源文件處 :D) 的地方編譯, 有人提出庫(kù)文件放在最后, 這樣出錯(cuò)先是項(xiàng)目?jī)?nèi)的文件, 頭文件都放在對(duì)應(yīng)源文件的最前面, 這一點(diǎn)足以保證內(nèi)部錯(cuò)誤的及時(shí)發(fā)現(xiàn)了.
以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

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

編程獅公眾號(hào)