【已解決】循環引用的疑問
最近在移植程式的過程中,碰到一個循環引用的問題,困擾了我不少時間,所以有寫下來的必要,以免下次再犯。直接來看例子吧!
這是最上層的介面 IElement,其中的 Method 可以處理實作 IVisitor 介面的物件
接下來是 IComputer 介面,繼承 IElement 介面
最後是 Computer 類別,實作 IComputer 介面。
另外還有一個 IVisitor 介面,它的 Method 可以用來處理實作 IComputer 介面的物件。
最後編譯的下場就是
1>ivisitor.h(6) : error C2061: syntax error : identifier 'PIComputer'
怎麼會這樣呢?(雖然原始 C# 的設計有點複雜,不過執行是沒問題的,沒想到移植到 C++ 卻發生這種狀況)
讓我們從 Computer 開始看一下引用的路徑吧
IComputer.h (Computer class) ==> IElement.h (IComputer struct) ==> IVisitor.h (IElement struct) ==> IComputer.h (IVisitor struct)
如果沒有 #pragma once 的話,將會造成循環引用,直到 Stack overflow 為止。而在 struct IVisitor 要引用 IComputer.h 時,發現前面已經引用過了,所以不會載入,所以 PIComputer 就變成未定義了。
解決辦法就是打破這個循環,我們修改一下 IElement 介面的定義
把原本的 include 改成用 struct IVisitor; 取代,那就萬事 OK 了
2013/4/17
ISensor 跟 IParameter 也發生相同的情況,解法同上
IHardware 跟 ISensor 也是一樣,看來問題很多啊
另外還有一個小插曲:
在 Computer class 有內嵌一個 Settings class 實作 ISettings 介面,其中 ISettings 前面的 public 不能省略,否則下列的敘述會產生錯誤
m_pSettings = new Settings();
1>computer.cpp(8) : error C2243: 'type cast' : conversion from 'Computer::Settings *' to '::PISettings' exists, but is inaccessible
推測應該是預設的實作繼承不是 public,所以才會造成轉型失敗
這裡有一篇 C++中基础类互相引用带来的问题 也不錯,講的是盡量不要在 Header 中再 include 其它 Header,可以參考看看!
不過我還是認為單一 .cpp 應該搭配單一 .h,而不是在 .h 中宣告 external class,然後又在 .cpp 中加入該 class 的 header file。所以研究了一個終極解法說明如下
以上是 ClassA 的宣告,而在它的 Hello 方法中會用到 ClassB
同樣 ClassB 的 Hello 方法也會用到 ClassA
用以上的方式宣告就可以自動帶入相關的 header file,而不須要由 .cpp file 去加入相關的 header file。讓使用 ClassA or ClassB 的程式,只要加入對應的 header file 就可以了。
不過要注意的是,參考 external class 的方法不能使用 inline 的寫法
void Hello(PClassB pClassB) { pClassB->Hello(); }
否則還是會產生錯誤訊息
error C2027: use of undefined type 'ClassB'
忘了把 namespace 考慮進來,結果一使用又 Compiler error 了。所以我們讓它再複雜一點吧,不同 namespace class 互相引用:
以上藍色是 ClassA 新增加的部份,雖然實驗了兩天才成功,不過最後看看增加的部份也還好
ClassB 的部份也是一樣。以後終於可以安心引用,不必擔心循環引用的問題了!
參考:C++ namespaces: cross-usage (Search keyword : namespace cross reference)
這是最上層的介面 IElement,其中的 Method 可以處理實作 IVisitor 介面的物件
#pragma once
#include "IVisitor.h" // <== Cause cross reference
struct IElement {
virtual VOID Accept(IVisitor* pVisitor) = 0;
virtual VOID Traverse(IVisitor* pVisitor) = 0;
};
typedef IElement* PIElement;
接下來是 IComputer 介面,繼承 IElement 介面
#pragma once #include "IElement.h" struct IComputer : public IElement { virtual BOOL getMainboardEnabled() = 0; }; typedef IComputer* PIComputer;
最後是 Computer 類別,實作 IComputer 介面。
#pragma once
#include "IComputer.h"
#include "ISettings.h"
#include "IVisitor.h"
class Computer : public IComputer {
class Settings : public ISettings {
public:
BOOL Contains(LPCTSTR name) { return FALSE; }
VOID SetValue(LPCTSTR name, LPCTSTR value) { }
CString GetValue(LPCTSTR name, LPCTSTR value) { return CString(value); }
VOID Remove (LPCTSTR name) { }
};
public:
Computer(void);
Computer(PISettings pSettings);
~Computer(void);
public: //Implement IComputer
VOID Accept(PIVisitor pVisitor) { }
VOID Traverse(PIVisitor pVisitor) { }
BOOL getMainboardEnabled() { return TRUE; }
private:
PISettings m_pSettings;
};
另外還有一個 IVisitor 介面,它的 Method 可以用來處理實作 IComputer 介面的物件。
#pragma once
#include "IComputer.h"
struct IVisitor {
virtual VOID VisitComputer(PIComputer pComputer) = 0;
};
最後編譯的下場就是
1>ivisitor.h(6) : error C2061: syntax error : identifier 'PIComputer'
讓我們從 Computer 開始看一下引用的路徑吧
IComputer.h (Computer class) ==> IElement.h (IComputer struct) ==> IVisitor.h (IElement struct) ==> IComputer.h (IVisitor struct)
如果沒有 #pragma once 的話,將會造成循環引用,直到 Stack overflow 為止。而在 struct IVisitor 要引用 IComputer.h 時,發現前面已經引用過了,所以不會載入,所以 PIComputer 就變成未定義了。
解決辦法就是打破這個循環,我們修改一下 IElement 介面的定義
#pragma once
//#include "IVisitor.h" // <== Cause cross reference
struct IVisitor; // <== Fix cross reference problem
struct IElement {
virtual VOID Accept(IVisitor* pVisitor) = 0;
virtual VOID Traverse(IVisitor* pVisitor) = 0;
};
typedef IElement* PIElement;
把原本的 include 改成用 struct IVisitor; 取代,那就萬事 OK 了
2013/4/17
ISensor 跟 IParameter 也發生相同的情況,解法同上
IHardware 跟 ISensor 也是一樣,看來問題很多啊
另外還有一個小插曲:
在 Computer class 有內嵌一個 Settings class 實作 ISettings 介面,其中 ISettings 前面的 public 不能省略,否則下列的敘述會產生錯誤
m_pSettings = new Settings();
1>computer.cpp(8) : error C2243: 'type cast' : conversion from 'Computer::Settings *' to '::PISettings' exists, but is inaccessible
推測應該是預設的實作繼承不是 public,所以才會造成轉型失敗
這裡有一篇 C++中基础类互相引用带来的问题 也不錯,講的是盡量不要在 Header 中再 include 其它 Header,可以參考看看!
不過我還是認為單一 .cpp 應該搭配單一 .h,而不是在 .h 中宣告 external class,然後又在 .cpp 中加入該 class 的 header file。所以研究了一個終極解法說明如下
#ifndef _CLASSA_H_ #define _CLASSA_H_ #ifndef _CLASSB_H_ #include "ClassB.h" #else class ClassB; typedef ClassB *PClassB; #endif class ClassA; typedef ClassA *PClassA; class ClassA { public: ClassA(void); ~ClassA(void); void Hello(PClassB pClassB); void Hello() { _tprintf(_T("Hello, I am ClassA.\n")); } }; #endif // _CLASSA_H_
以上是 ClassA 的宣告,而在它的 Hello 方法中會用到 ClassB
#ifndef _CLASSB_H_ #define _CLASSB_H_ #ifndef _CLASSA_H_ #include "ClassA.h" #else class ClassA; typedef ClassA *PClassA; #endif class ClassB; typedef ClassB *PClassB; class ClassB { public: ClassB(void); ~ClassB(void); void Hello(PClassA pClassA); void Hello() { _tprintf(_T("Hello, I am ClassB.\n")); } }; #endif // _CLASSB_H_
同樣 ClassB 的 Hello 方法也會用到 ClassA
用以上的方式宣告就可以自動帶入相關的 header file,而不須要由 .cpp file 去加入相關的 header file。讓使用 ClassA or ClassB 的程式,只要加入對應的 header file 就可以了。
不過要注意的是,參考 external class 的方法不能使用 inline 的寫法
void Hello(PClassB pClassB) { pClassB->Hello(); }
否則還是會產生錯誤訊息
error C2027: use of undefined type 'ClassB'
忘了把 namespace 考慮進來,結果一使用又 Compiler error 了。所以我們讓它再複雜一點吧,不同 namespace class 互相引用:
#ifndef _CLASSA_H_ #define _CLASSA_H_ #ifndef _CLASSB_H_ #include "ClassB.h" #else namespace NameSpaceB { class ClassB; typedef ClassB *PClassB; } #endif using namespace NameSpaceB; namespace NameSpaceA { class ClassA; typedef ClassA *PClassA; class ClassA { public: ClassA(void); ~ClassA(void); void Hello(PClassB pClassB); void Hello() { _tprintf(_T("Hello, I am ClassA.\n")); } }; } #endif // _CLASSA_H_
以上藍色是 ClassA 新增加的部份,雖然實驗了兩天才成功,不過最後看看增加的部份也還好
#ifndef _CLASSB_H_ #define _CLASSB_H_ #ifndef _CLASSA_H_ #include "ClassA.h" #else namespace NameSpaceA { class ClassA; typedef ClassA *PClassA; } #endif using namespace NameSpaceA; namespace NameSpaceB { class ClassB; typedef ClassB *PClassB; class ClassB { public: ClassB(void); ~ClassB(void); void Hello(PClassA pClassA); void Hello() { _tprintf(_T("Hello, I am ClassB.\n")); } }; } #endif // _CLASSB_H_
ClassB 的部份也是一樣。以後終於可以安心引用,不必擔心循環引用的問題了!
參考:C++ namespaces: cross-usage (Search keyword : namespace cross reference)
留言