c++中,用於防止頭檔案重複包含的機制 稱為頭檔案警衛(header file guard)。
基本介紹
- 中文名:頭檔案警衛
- 定義:防止頭檔案重複包含的機制
- 使用:條件編譯命令
- 分別:定義在各自的頭檔案A.h和B.h中
假設,嵌套,編譯過程,兩點原則,
假設
假設我們有兩個類A和B,分別定義在各自的頭檔案A.h和B.h中,但是在A中要用到B,B中也要用到A,像下面的寫法是錯誤的:
class B; class A { public: B b; }; class B { public: A a; }; |
因為在A對象中要開闢一塊屬於B的空間,而B中又有A的空間,是一個邏輯錯誤,無法實現的。在這裡我們只需要把其中的一個A類中的B類型成員改成指針形式 就可以避免這個無限延伸的怪圈了。要更改A而不是B?因為就算你在B中做了類似的動作,也仍然會編譯錯誤,表面上這僅僅上一個先後順序的問題。
為什麼會這樣呢?因為C++編譯器自上而下編譯源檔案的時候,對每一個數據的定義,總是需要知道定義的數據的類型的大小。在預先聲明語句class B;之後,編譯器已經知道B是一個類,但是其中的數據卻是未知的,因此B類型的大小也不知道。這樣就造成了編譯失敗,VC++6.0下會得到如下編譯錯 誤:
error C2079: 'b' uses undefined class 'B' |
將A中的b更改為B指針類型之後,由於在特定的平台上,指針所占的空間是一定的(在Win32平台上是4位元組),這樣可以通過編譯。
嵌套
在實際編程中,不同的類一般是放在不同的相互獨立的頭檔案中的,這樣兩個類在相互引用時又會有不一樣的問題。重複編譯是問題出現的根本原因。為了保證頭文 件僅被編譯一次,在C++中常用的辦法是使用條件編譯命令。
Example:
animal.h class animal { ...... }; animal.cpp #include "animal.h" #include ...... fish.h #include "animal.h" class fish { ...... }; fish.cpp #include "fish.h" #include ...... main.cpp #include "animal.h" #include "fish.h" void main() { ...... } |
編譯檔案,會出現class type redefinition的錯誤
為什麼會出現類重複定義的錯誤呢?請讀者仔細查看EX10.cpp檔案,在這個檔案中包含了animal.h和fish.h這兩個頭檔案。當編譯器編譯EX10.cpp檔案時,因為在檔案中包含了animal.h頭檔案,編譯器展開這個頭檔案,知道animal這個類定義了,接著展開fish.h 頭檔案,而在fish.h頭檔案中也包含了animal.h,再次展開animal.h,於是animal這個類就重複定義了。
要解決頭檔案重複包含的問題,可以使用條件預處理指令。
修改後的頭檔案如下:
animal.h #ifndef ANIMAL_H_H #define ANIMAL_H_H class animal { ...... }; #endif fish.h #include "animal.h" #ifndef FISH_H_H #define FISH_H_H class fish { ...... }; #endif |
編譯過程
。當編譯器展開animal.h頭檔案時,條件預處理指令判斷ANIMAL_H_H沒有定義,於是就定 義它,然後繼續執行,定義了animal這個類;接著展開fish.h頭檔案,而在fish.h頭檔案中也包含了animal.h,再次展開 animal.h,這個時候條件預處理指令發現ANIMAL_H_H已經定義,於是跳轉到#endif,執行結束。
但是不要以為使用了這種機制就全部搞定了,比如在以下的代碼中:
//檔案A.h中的代碼 #pragma once #include "B.h" class A { public: B* b; }; //檔案B.h中的代碼 #pragma once #include "A.h" class B { public: A* a; }; |
error C2501: 'A' : missing storage-class or type specifiers |
仍然是類型不能找到的錯誤。其實這裡仍然需要前置聲明。分別添加前置聲明之後,可以成功編譯了。代碼形式如下:
//檔案A.h中的代碼 #pragma once #include "B.h" class B; class A { public: B* b; }; //檔案B.h中的代碼 #pragma once #include "A.h" class A; class B { public: A* a; }; |
這樣至少可以說明,頭檔案包含代替不了前置聲明。有的時候只能依靠前置聲明來解決問題。我們還要思考一下,有了前置聲明的時候頭檔案包含還是必要的 嗎?我們嘗試去掉A.h和B.h中的#include行,發現沒有出現新的錯誤。那么究竟什麼時候需要前置聲明,什麼時候需要頭檔案包含呢?
兩點原則
頭檔案包含其實是一件很煩瑣的工作,不但我們看著累,編譯器編譯的時候也很累,再加上頭檔案中常常出現的宏定義。感覺各種宏定義的展開是非常耗時間的,遠不如自定義函式來得速度。我僅就不同頭檔案、源檔案間的句則結構問題提出兩點原則,僅供參考:
第一個原則
如果可以不包含頭檔案,那就不要包含了。這時候前置聲明可以解決問題。如果使用的僅僅是一個類的指針,沒有使用這個類的具體對象(非指針),也沒有訪問到類的具體成員,那么前置聲明就可以了。因為指針這一數據類型的大小是特定的,編譯器可以獲知。
第二個原則
儘量在CPP檔案中包含頭檔案,而非在頭檔案中。假設類A的一個成員是是一個指向類B的指針,在類A的頭檔案中使用了類B的前置聲明並編譯成功,那么在A的實現中我們需要訪問B的具體成員,因此需要包含頭檔案,那么我們應該在類A的實現部分(CPP檔案)包含類B的頭檔案而非聲明部分 (H檔案)。