通常每一個 .cc
文件都有一個對應的 .h
文件. 也有一些常見例外, 如單元測試代碼和只包含 main()
函數(shù)的 .cc
文件.
正確使用頭文件可令代碼在可讀性、文件大小和性能上大為改觀.
下面的規(guī)則將引導你規(guī)避使用頭文件時的各種陷阱.
Tip
所有頭文件都應該使用
#define
防止頭文件被多重包含, 命名格式當是:<PROJECT>_<PATH>_<FILE>_H_
為保證唯一性, 頭文件的命名應該依據(jù)所在項目源代碼樹的全路徑. 例如, 項目 foo
中的頭文件 foo/src/bar/baz.h
可按如下方式保護:
#ifndef FOO_BAR_BAZ_H_
#define FOO_BAR_BAZ_H_
…
#endif // FOO_BAR_BAZ_H_
Tip
能用前置聲明的地方盡量不使用 #include.
當一個頭文件被包含的同時也引入了新的依賴, 一旦該頭文件被修改, 代碼就會被重新編譯. 如果這個頭文件又包含了其他頭文件, 這些頭文件的任何改變都將導致所有包含了該頭文件的代碼被重新編譯. 因此, 我們傾向于減少包含頭文件, 尤其是在頭文件中包含頭文件.
使用前置聲明可以顯著減少需要包含的頭文件數(shù)量. 舉例說明: 如果頭文件中用到類?File
, 但不需要訪問?File
?類的聲明, 頭文件中只需前置聲明?class?File;
?而無須?#include"file/base/file.h"
.
不允許訪問類的定義的前提下, 我們在一個頭文件中能對類?Foo
?做哪些操作?
Foo?*
?或?Foo?&
.Foo
?(但不能定義實現(xiàn)).Foo
, 因為靜態(tài)數(shù)據(jù)成員的定義在類定義之外.反之, 如果你的類是?Foo
?的子類, 或者含有類型為?Foo
?的非靜態(tài)數(shù)據(jù)成員, 則必須包含?Foo
?所在的頭文件.
有時, 使用指針成員 (如果是?scoped_ptr
?更好) 替代對象成員的確是明智之選. 然而, 這會降低代碼可讀性及執(zhí)行效率, 因此如果僅僅為了少包含頭文件,還是不要這么做的好.
當然?.cc
?文件無論如何都需要所使用類的定義部分, 自然也就會包含若干頭文件.
Tip
只有當函數(shù)只有 10 行甚至更少時才將其定義為內(nèi)聯(lián)函數(shù).
定義:
當函數(shù)被聲明為內(nèi)聯(lián)函數(shù)之后, 編譯器會將其內(nèi)聯(lián)展開, 而不是按通常的函數(shù)調(diào)用機制進行調(diào)用.
優(yōu)點:
當函數(shù)體比較小的時候, 內(nèi)聯(lián)該函數(shù)可以令目標代碼更加高效. 對于存取函數(shù)以及其它函數(shù)體比較短, 性能關鍵的函數(shù), 鼓勵使用內(nèi)聯(lián).
缺點:
濫用內(nèi)聯(lián)將導致程序變慢. 內(nèi)聯(lián)可能使目標代碼量或增或減, 這取決于內(nèi)聯(lián)函數(shù)的大小. 內(nèi)聯(lián)非常短小的存取函數(shù)通常會減少代碼大小, 但內(nèi)聯(lián)一個相當大的函數(shù)將戲劇性的增加代碼大小. 現(xiàn)代處理器由于更好的利用了指令緩存, 小巧的代碼往往執(zhí)行更快。
結(jié)論:
一個較為合理的經(jīng)驗準則是, 不要內(nèi)聯(lián)超過 10 行的函數(shù). 謹慎對待析構(gòu)函數(shù), 析構(gòu)函數(shù)往往比其表面看起來要更長, 因為有隱含的成員和基類析構(gòu)函數(shù)被調(diào)用!
另一個實用的經(jīng)驗準則: 內(nèi)聯(lián)那些包含循環(huán)或?switch
?語句的函數(shù)常常是得不償失 (除非在大多數(shù)情況下, 這些循環(huán)或?switch
?語句從不被執(zhí)行).
有些函數(shù)即使聲明為內(nèi)聯(lián)的也不一定會被編譯器內(nèi)聯(lián), 這點很重要; 比如虛函數(shù)和遞歸函數(shù)就不會被正常內(nèi)聯(lián). 通常, 遞歸函數(shù)不應該聲明成內(nèi)聯(lián)函數(shù).(YuleFox 注: 遞歸調(diào)用堆棧的展開并不像循環(huán)那么簡單, 比如遞歸層數(shù)在編譯時可能是未知的, 大多數(shù)編譯器都不支持內(nèi)聯(lián)遞歸函數(shù)). 虛函數(shù)內(nèi)聯(lián)的主要原因則是想把它的函數(shù)體放在類定義內(nèi), 為了圖個方便, 抑或是當作文檔描述其行為, 比如精短的存取函數(shù).
Tip
復雜的內(nèi)聯(lián)函數(shù)的定義, 應放在后綴名為?
-inl.h
?的頭文件中.
內(nèi)聯(lián)函數(shù)的定義必須放在頭文件中, 編譯器才能在調(diào)用點內(nèi)聯(lián)展開定義. 然而, 實現(xiàn)代碼理論上應該放在?.cc
?文件中, 我們不希望?.h
?文件中有太多實現(xiàn)代碼, 除非在可讀性和性能上有明顯優(yōu)勢.
如果內(nèi)聯(lián)函數(shù)的定義比較短小, 邏輯比較簡單, 實現(xiàn)代碼放在?.h
?文件里沒有任何問題. 比如, 存取函數(shù)的實現(xiàn)理所當然都應該放在類定義內(nèi). 出于編寫者和調(diào)用者的方便, 較復雜的內(nèi)聯(lián)函數(shù)也可以放到?.h
?文件中, 如果你覺得這樣會使頭文件顯得笨重, 也可以把它萃取到單獨的?-inl.h
?中. 這樣把實現(xiàn)和類定義分離開來, 當需要時包含對應的?-inl.h
?即可。
-inl.h
?文件還可用于函數(shù)模板的定義. 從而增強模板定義的可讀性.
別忘了?-inl.h
?和其他頭文件一樣, 也需要?#define
?保護.
Tip
定義函數(shù)時, 參數(shù)順序依次為: 輸入?yún)?shù), 然后是輸出參數(shù).
C/C++ 函數(shù)參數(shù)分為輸入?yún)?shù), 輸出參數(shù), 和輸入/輸出參數(shù)三種. 輸入?yún)?shù)一般傳值或傳?const
?引用, 輸出參數(shù)或輸入/輸出參數(shù)則是非-const
?指針. 對參數(shù)排序時, 將只輸入的參數(shù)放在所有輸出參數(shù)之前. 尤其是不要僅僅因為是新加的參數(shù), 就把它放在最后; 即使是新加的只輸入?yún)?shù)也要放在輸出參數(shù)之前.
這條規(guī)則并不需要嚴格遵守. 輸入/輸出兩用參數(shù) (通常是類/結(jié)構(gòu)體變量) 把事情變得復雜, 為保持和相關函數(shù)的一致性, 你有時不得不有所變通.
#include
?的路徑及順序Tip
使用標準的頭文件包含順序可增強可讀性, 避免隱藏依賴: C 庫, C++ 庫, 其他庫的?.h, 本項目內(nèi)的?.h.
項目內(nèi)頭文件應按照項目源代碼目錄樹結(jié)構(gòu)排列, 避免使用 UNIX 特殊的快捷目錄?.
?(當前目錄) 或?..
?(上級目錄). 例如,?google-awesome-project/src/base/logging.h
?應該按如下方式包含:
#include "base/logging.h"
又如,?dir/foo.cc
?的主要作用是實現(xiàn)或測試?dir2/foo2.h
?的功能,?foo.cc
?中包含頭文件的次序如下:
dir2/foo2.h
?(優(yōu)先位置, 詳情如下).h
?文件.h
?文件這種排序方式可有效減少隱藏依賴. 我們希望每一個頭文件都是可被獨立編譯的 (yospaly 譯注: 即該頭文件本身已包含所有必要的顯式依賴), 最簡單的方法是將其作為第一個?.h
?文件?#included
?進對應的?.cc
.
dir/foo.cc
?和?dir2/foo2.h
?通常位于同一目錄下 (如?base/basictypes_unittest.cc
?和?base/basictypes.h
), 但也可以放在不同目錄下.
按字母順序?qū)︻^文件包含進行二次排序是不錯的主意 (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);.
?和?..
?雖然方便卻易混亂, 使用比較完整的項目路徑看上去很清晰, 很條理, 包含文件的次序除了美觀之外, 最重要的是可以減少隱藏依賴, 使每個頭文件在 “最需要編譯” (對應源文件處 :D) 的地方編譯, 有人提出庫文件放在最后, 這樣出錯先是項目內(nèi)的文件, 頭文件都放在對應源文件的最前面, 這一點足以保證內(nèi)部錯誤的及時發(fā)現(xiàn)了.
更多建議: