大型項目開發: 頭文件順序

經驗告訴我們,某些編碼實踐雖然在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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章