1. 程式人生 > >C/C++編程規範——頭文件

C/C++編程規範——頭文件

path 方便 一點 文檔 代碼量 必須 隱式 def inter

在選擇編程規範時,我首選google,其次是華為與微軟,最後根據自身的一些實際情況進行調整。以下內容摘自google的c/c++編程規範。
——————————————————————————
通常,每一個.cc 文件(C++的源文件)都有一個對應的.h 文件(頭文件),也有一些例外,如單元測試代碼和只包含 main()的.cc 文件。
正確使用頭文件可令代碼在可讀性、文件大小和性能上大為改觀。下面的規則將引導你規避使用頭文件時的各種麻煩。

1. #define 的保護
所有頭文件都應該使用#define 防止頭文件被多重包含(multiple inclusion),命名格式應當是:

<PROJECT>_<PATH>_<FILE>_H_

為保證唯一性,頭文件的命名應基於其所在項目源代碼樹的全路徑。例如,項目 foo 中的頭文件 foo/src/bar/baz.h 按如下方式保護:

#ifndef FOO_BAR_BAZ_H_
#define FOO_BAR_BAZ_H_
...
#endif // FOO_BAR_BAZ_H_

2. 頭文件依賴
使用前置聲明(forward declarations)盡量減少.h 文件中#include 的數量。
當一個頭文件被包含的同時也引入了一項新的依賴(dependency),只要該頭文件被修改,代碼就要重新編譯。如果你的頭文件包含了其他頭文件,這些頭文件的任何改變也將導致那些包含了你的頭文件的代碼重新編譯。因此,我們寧可盡量少包含頭文件,尤其是那些包含在其他頭文件中的。

使用前置聲明可以顯著減少需要包含的頭文件數量。舉例說明:頭文件中用到類 File,但不需要訪問 File 的聲明,則頭文件中只需前置聲明 class File;無需#include
"file/base/file.h"。
在頭文件如何做到使用類 Foo 而無需訪問類的定義?
1) 將數據成員類型聲明為 Foo *或 Foo &;
2) 參數、返回值類型為 Foo 的函數只是聲明(但不定義實現);
3) 靜態數據成員的類型可以被聲明為 Foo,因為靜態數據成員的定義在類定義之外。另一方面,如果你的類是 Foo 的子類,或者含有類型為 Foo 的非靜態數據成員,則必須為之包含頭文件。
有時,使用指針成員(pointer members,如果是 scoped_ptr 更好)替代對象成員(object members)的確更有意義。然而,這樣的做法會降低代碼可讀性及執行效率。如果僅僅為了少包含頭文件,還是不要這樣替代的好。
當然,.cc 文件無論如何都需要所使用類的定義部分,自然也就會包含若幹頭文件。
譯者註:能依賴聲明的就不要依賴定義。

3. 內聯函數
只有當函數只有 10 行甚至更少時才會將其定義為內聯函數(inline function)。
定義(Definition):當函數被聲明為內聯函數之後,編譯器可能會將其內聯展開,無需按通常的函數調用機制調用內聯函數。
優點:當函數體比較小的時候,內聯該函數可以令目標代碼更加高效。對於存取函數(accessor、mutator)以及其他一些比較短的關鍵執行函數。
缺點:濫用內聯將導致程序變慢,內聯有可能是目標代碼量或增或減,這取決於被內聯的函數的大小。內聯較短小的存取函數通常會減少代碼量,但內聯一個很大的函數(譯者註:如果編譯器允許的話)將戲劇性的增加代碼量。在現代處理器上,由於更好的利用指令緩存(instruction cache),小巧的代碼往往執行更快。
結論:一個比較得當的處理規則是,不要內聯超過 10 行的函數。對於析構函數應慎重對待,析構函數往往比其表面看起來要長,因為有一些隱式成員和基類析構函數(如果有的話)被調用!
另一有用的處理規則:內聯那些包含循環或 switch 語句的函數是得不償失的,除非在大多數情況下,這些循環或 switch 語句從不執行。
重要的是,虛函數和遞歸函數即使被聲明為內聯的也不一定就是內聯函數。通常,遞歸函數不應該被聲明為內聯的(譯者註:遞歸調用堆棧的展開並不像循環那麽簡單,比如遞歸層數在編譯時可能是未知的,大多數編譯器都不支持內聯遞歸函數)。析構函數內聯的主要原因是其定義在類的定義中,為了方便抑或是對其行為給出文檔。

4. -inl.h 文件
復雜的內聯函數的定義,應放在後綴名為-inl.h 的頭文件中。
在頭文件中給出內聯函數的定義,可令編譯器將其在調用處內聯展開。然而,實現代碼應完全放到.cc 文件中,我們不希望.h 文件中出現太多實現代碼,除非這樣做在可讀性和效率上有明顯優勢。
如果內聯函數的定義比較短小、邏輯比較簡單,其實現代碼可以放在.h 文件中。例如,存取函數的實現理所當然都放在類定義中。出於實現和調用的方便,較復雜的內聯函數也可以放到.h 文件中,如果你覺得這樣會使頭文件顯得笨重,還可以將其分離到單獨的-inl.h 中。
這樣即把實現和類定義分離開來,當需要時包含實現所在的-inl.h 即可。
-inl.h 文件還可用於函數模板的定義,從而使得模板定義可讀性增強。
要提醒的一點是,-inl.h 和其他頭文件一樣,也需要#define 保護。

5. 函數參數順序(Function Parameter Ordering)
定義函數時,參數順序為:輸入參數在前,輸出參數在後。
C/C++函數參數分為輸入參數和輸出參數兩種,有時輸入參數也會輸出(譯者註:值被修改時)。輸入參數一般傳值或常數引用(const references),輸出參數或輸入/輸出參數為非常數指針(non-const pointers)。對參數排序時,將所有輸入參數置於輸出參數之前。不要僅僅因為是新添加的參數,就將其置於最後,而應該依然置於輸出參數之前。
這一點並不是必須遵循的規則,輸入/輸出兩用參數(通常是類/結構體變量)混在其中,會使得規則難以遵循。

6. 包含文件的名稱及次序
將包含次序標準化可增強可讀性、避免隱藏依賴(hidden dependencies,譯者註:隱藏依賴主要是指包含的文件中編譯時),次序如下:C 庫 、C++庫、其他庫的.h、項目內的.h。
項目內頭文件應按照項目源代碼目錄樹結構排列,並且避免使用 UNIX 文件路徑.(當前目錄)和..(父目錄)。例如,google-awesome-project/src/base/logging.h 應像這樣
被包含:
#include "base/logging.h"
dir/foo.cc 的主要作用是執行或測試 dir2/foo2.h 的功能,foo.cc 中包含頭文件的次序如
下:
dir2/foo2.h(優先位置,詳情如下)
C 系統文件
C++系統文件
其他庫頭文件
本項目內頭文件
這種排序方式可有效減少隱藏依賴,我們希望每一個頭文件獨立編譯。最簡單的實現方式是將其作為第一個.h 文件包含在對應的.cc 中。
dir/foo.cc 和 dir2/foo2.h 通常位於相同目錄下(像 base/basictypes_unittest.cc 和base/basictypes.h),但也可在不同目錄下。
相同目錄下頭文件按字母序是不錯的選擇。
舉例來說,google-awesome-project/src/foo/internal/fooserver.cc 的包含次序如下:

#include "foo/public/fooserver.h" // 優先位置
#include <sys/types.h>
#include <unistd.h>
#include <hash_map>
#include <vector>
#include "base/basictypes.h"
#include "base/commandlineflags.h"
#include "foo/public/bar.h"

————————————————————————————
我把博客當作筆記本用,裏面的內容會隨著實際情況而發生變化。

C/C++編程規範——頭文件