通常每一個(gè) .cc
文件都有一個(gè)對(duì)應(yīng)的 .h
文件. 也有一些常見例外, 如單元測(cè)試代碼和只包含 main()
函數(shù)的 .cc
文件.
正確使用頭文件可令代碼在可讀性、文件大小和性能上大為改觀.
下面的規(guī)則將引導(dǎo)你規(guī)避使用頭文件時(shí)的各種陷阱.
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_
Tip
能用前置聲明的地方盡量不使用 #include.
當(dāng)一個(gè)頭文件被包含的同時(shí)也引入了新的依賴, 一旦該頭文件被修改, 代碼就會(huì)被重新編譯. 如果這個(gè)頭文件又包含了其他頭文件, 這些頭文件的任何改變都將導(dǎo)致所有包含了該頭文件的代碼被重新編譯. 因此, 我們傾向于減少包含頭文件, 尤其是在頭文件中包含頭文件.
使用前置聲明可以顯著減少需要包含的頭文件數(shù)量. 舉例說明: 如果頭文件中用到類?File
, 但不需要訪問?File
?類的聲明, 頭文件中只需前置聲明?class?File;
?而無須?#include"file/base/file.h"
.
不允許訪問類的定義的前提下, 我們?cè)谝粋€(gè)頭文件中能對(duì)類?Foo
?做哪些操作?
Foo?*
?或?Foo?&
.Foo
?(但不能定義實(shí)現(xiàn)).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ì)包含若干頭文件.
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ù).
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ù).
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í)不得不有所變通.
#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
?中包含頭文件的次序如下:
dir2/foo2.h
?(優(yōu)先位置, 詳情如下).h
?文件.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"
-inl.h
?可提高代碼可讀性 (一般用不到吧:D);.
?和?..
?雖然方便卻易混亂, 使用比較完整的項(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)了.
更多建議: