1. 程式人生 > >c++中兩個類的標頭檔案互相包含編譯出錯的解決辦法

c++中兩個類的標頭檔案互相包含編譯出錯的解決辦法

首先我們需要問一個問題是:為什麼兩個類不能互相包含標頭檔案?

所謂互相包含標頭檔案,我舉一個例子:我實現了兩個類:圖層類CLayer和符號類CSymbol,它們的大致關係是圖層裡包含有符號,符號裡定義一個相關圖層指標,具體請參考如下程式碼(注:以下程式碼僅供說明問題,不作為類設計參考,所以不適宜以此討論類的設計,編譯環境為Microsoft Visual C++ 2005,,Windows XP + sp2,以下同):

  1. //Layer.h 
  2. // 圖層類 
  3. #pragma once 
  4. #include"Symbol.h" 
  5. classCLayer 
  6. { 
  7. public: 
  8. CLayer(void); 
  9. virtual~CLayer(void); 
  10. voidCreateNewSymbol(); 
  11. private: 
  12. CSymbol*    m_pSymbol;// 該圖層相關的符號指標 
  13. }; 
  14. // Symbol.h 
  15. // 符號類 
  16. #pragma once 
  17. #include"Layer.h" 
  18. classCSymbol 
  19. { 
  20. public: 
  21. CSymbol(void); 
  22. virtual~CSymbol(void); 
  23. public: 
  24. CLayer*m_pRelLayer;// 符號對應的相關圖層 
  25. }; 
  26. // TestUnix.cpp : 定義控制檯應用程式的入口點。 
  27. // 
  28. #include"stdafx.h"
     
  29. #include"Layer.h" 
  30. #include"Symbol.h" 
  31. void main(void) 
  32. { 
  33. CLayerMyLayer; 
  34. }

現在開始編譯,編譯出錯,現在讓我們分析一下編譯出錯資訊(我發現分析編譯資訊對加深程式的編譯過程的理解非常有好處)。
首先我們明確:編譯器在編譯檔案時,遇到#include "x.h"時,就開啟x.h檔案進行編譯,這相當於把x.h檔案的內容放在#include "x.h"處。
編譯資訊告訴我們:它是先編譯TestUnix.cpp檔案的,那麼接著它應該編譯stdafx.h,接著是Layer.h,如果編譯Layer.h,那麼會編譯Symbol.h,但是編譯Symbol.h又應該編譯Layer.h啊,這豈不是陷入一個死迴圈?
呵呵,如果沒有預編譯指令,是會這樣的,實際上在編譯Symbol.h,再去編譯Layer.h,Layer.h頭上的那個#pragma once就會告訴編譯器:老兄,這個你已經編譯過了,就不要再浪費力氣編譯了!那麼編譯器得到這個資訊就會不再編譯Layer.h而轉回到編譯Symbol.h的餘下內容。
當編譯到CLayer *m_pRelLayer;這一行編譯器就會迷惑了:CLayer是什麼東西呢?我怎麼沒見過呢?那麼它就得給出一條出錯資訊,告訴你CLayer沒經定義就用了呢?
在TestUnix.cpp中#include "Layer.h"這句算是宣告編譯結束(呵呵,簡單一句彎彎繞繞不斷),下面輪到#include "Symbol.h",由於預編譯指令的阻擋,Symbol.h實際上沒有得到編譯,接著再去編譯TestUnix.cpp的餘下內容。
當然上面僅僅是我的一些推論,還沒得到完全證實,不過我們可以稍微測試一下,假如在TestUnix.cpp將#include "Layer.h"和#include "Symbol.h"互換一下位置,那麼會不會先提示CSymbol類沒有定義呢?實際上是這樣的。當然這個也不能完全證實我的推論。

照這樣看,兩個類的互相包含標頭檔案肯定出錯,那麼如何解決這種情況呢?

一種辦法是在A類中包含B類的標頭檔案,在B類中前置盛明A類,不過注意的是B類使用A類變數必須通過指標來進行,具體見拙文:類互相包含的辦法。

為何不能前置宣告只能通過指標來使用?通過分析這個實際上我們可以得出前置宣告和包含標頭檔案的區別。

我們把CLayer類的程式碼改動一下,再看下面的程式碼:

  1. // 圖層類 
  2. //Layer.h 
  3. #pragma once 
  4. //#include "Symbol.h" 
  5. classCSymbol; 
  6. classCLayer 
  7. { 
  8. public: 
  9. CLayer(void); 
  10. virtual~CLayer(void); 
  11. //    void SetSymbol(CSymbol *pNewSymbol); 
  12. voidCreateNewSymbol(); 
  13. private: 
  14. CSymbol*    m_pSymbol;// 該圖層相關的符號 
  15. //    CSymbol m_Symbol; 
  16. }; 
  17. // Layer.cpp 
  18. #include"StdAfx.h" 
  19. #include"Layer.h" 
  20. CLayer::CLayer(void) 
  21. { 
  22.     m_pSymbol = NULL; 
  23. } 
  24. CLayer::~CLayer(void) 
  25. { 
  26. if(m_pSymbol!=NULL) 
  27. { 
  28. delete m_pSymbol; 
  29.         m_pSymbol=NULL; 
  30. } 
  31. } 
  32. voidCLayer::CreateNewSymbol() 
  33. { 
  34. }

然後編譯,出現一個編譯警告:>f:\mytest\mytest\src\testunix\layer.cpp(16) : warning C4150: 刪除指向不完整“CSymbol”型別的指標;沒有呼叫解構函式
1> f:\mytest\mytest\src\testunix\layer.h(9) : 參見“CSymbol”的宣告
看到這個警告,我想你一定悟到了什麼。下面我說說我的結論:

類的前置宣告和包含標頭檔案的區別在於類的前置宣告是告訴編譯器有這種型別,但是它沒有告訴編譯器這種型別的大小、成員函式和資料成員,而包含標頭檔案則是完全告訴了編譯器這種型別到底是怎樣的(包括大小和成員)。
這下我們也明白了為何前置宣告只能使用指標來進行,因為指標大小在編譯器是確定的。
上面正因為前置宣告不能提供解構函式資訊,所以編譯器提醒我們:“CSymbol”型別的指標是沒有呼叫解構函式。

如何解決這個問題呢?

在Layer.cpp加上#include "Symbol.h"就可以消除這個警告。