1. 頭文件

2018-02-24 15:11 更新

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

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

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

1.1. #define 保護

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_

1.2. 頭文件依賴

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

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

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

不允許訪問類的定義的前提下, 我們在一個頭文件中能對類?Foo?做哪些操作?

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

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

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

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

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

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ù).

1.4. -inl.h文件

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?保護.

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

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ù)的一致性, 你有時不得不有所變通.

1.6.?#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?中包含頭文件的次序如下:

  1. dir2/foo2.h?(優(yōu)先位置, 詳情如下)
  2. C 系統(tǒng)文件
  3. C++ 系統(tǒng)文件
  4. 其他庫的?.h?文件
  5. 本項目內(nèi)?.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"

譯者 (YuleFox) 筆記

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

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號