大型專案開發: 標頭檔案順序

Horky發表於2015-07-09

經驗告訴我們,某些編碼實踐雖然在C++中完全合法,但是絕對不能應用於大型專案環境中。 大型專案環境下必須有適當的約束,否則很容易變得難以控制並很難維護(摘自<<大規模C++程式設計>>)。下面以Chromium中運用的兩個Coding Style中定義的標頭檔案順序為例。

標頭檔案順序的差異

WebKit/Blink遵循業界標準的定義,其實也是Lakos在<<大規模C++程式設計>>中建議的順序 :

  1. 編譯單元對應的標頭檔案 (Related header file)。
  2. 工程內的其它標頭檔案。
  3. 庫及系統標頭檔案。

(Blink特殊的一點是編譯單元必須先包含config.h。)

這樣做的目的是為避免隱性依賴。每個標頭檔案都應當做到自包含(Self-Contained, or Self Sufficient), 這樣保證使用者能直接標頭檔案中理解它的物理依賴。如果遵循這個順序,當出現隱性依賴時,是無法編譯通過的。

下面這個例子:
my_class.h中依賴了std::string, 但沒有顯式的包含,當main裡先包含標準庫的標頭檔案string時,編譯是不會出錯的。

main.cc

#include <string>
#include <iostream>
#include "my_class.h"

int main(int argc, char* argv[]) {
  MyClass aInstance;
  std::cout << aInstance.value() << std::endl;
}

my_class.h

#ifndef MY_CLASS_H_
#define MY_CLASS_H_
class MyClass {
 public:
  MyClass();
  const std::string& value();
 private:
  std::string value_;
};
#endif

如果遵循上面的規則,就會在編譯main.cc時報錯:
In file included from main.cc:1:
./my_class.h:6:9: error: use of undeclared identifier 'std'
  const std::string& value();

在一個層次更為清晰的專案下,錯誤最好歸屬到作者身上。這裡main.cc做為my_class.h的使用者,這樣的錯誤最好由my_class.h的作者來解決。所以Google定義瞭如下的規則:
關聯的標頭檔案
C庫標頭檔案
C++庫標頭檔案
其它庫標頭檔案
* 專案中的其它標頭檔案。
實現如下的my_class.cc時就會收到報錯:
my_class.cc
#include "my_class.h"
#include <string>


MyClass::MyClass()
  :value_("Hello!") {
}

在這種情況下,這個錯誤將只報給my_class.h對應的編譯單元my_class.cc。
In file included from my_class.cc:1:
./my_class.h:6:9: error: use of undeclared identifier 'std'
  const std::string& value();

如果是一個小專案,可能察覺不到這樣做的差異。但在講求職責清晰,分工協作的大型專案下,它就會變得很有價值。
這是一個很小的點,可見大型專案中一些規則定義的冰山一角。再比如大型專案中的標頭檔案還有一些設計上的權衡。比如避免不必要包含標頭檔案會拖慢專案的構建效率,引入了前置宣告。但它卻在一些場景下會造成歧義(比如前置宣告標準庫中的類,或者模板類),或者引入一些的維護成本和風險(比如函式的引數變化,需要對應修改前置宣告)。Google早期是鼓勵使用前置宣告,而現在只是強調在明確帶來好處的情況下才建議使用前置宣告。

擴充套件閱讀:

Self-sufficient header files in C/C++
Headers and Includes: Why and How
C/C++ include file order/best practices

相關文章