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