當兩個類別互相引用對方(A 包含 B,B 包含 A)時,直接 #include 會造成編譯器陷入遞迴包含循環。C++ 提供前置宣告機制打破此死結。本文深入解析前置宣告的正確用法、三大陷阱、相互依賴解決方案,以及友元類別中的應用。
// A.h
#include "B.h" // 錯誤:B.h 又會 include A.h
class A { B* b; };
// B.h
#include "A.h"
class B { A a; };
編譯錯誤:error C2146: syntax error : missing ';' before identifier 'b'
class CBed; // 告訴編譯器 CBed 是一個類別型別
之後在類別中只能使用指標或參考,不可直接宣告物件實體(因為編譯器不知道大小)。
// House.h
class CBed; // 前置宣告
class CHouse {
CBed* bed; // 必須用指標
public:
CHouse();
~CHouse();
void GoToBed();
};
// House.cpp
#include "Bed.h" // 實作檔才 include 完整定義
#include "House.h"
CHouse::CHouse() : bed(new CBed()) {}
void CHouse::GoToBed() { bed->Sleep(); }
// Bed.h
class CBed {
public:
CBed(); ~CBed();
void Sleep();
};
class CHouse {
CBed* bed; // ✓ 正確
CBed& bed; // ✓ 參考也可(需建構子初始化)
CBed bed; // ✗ 錯誤:需要知道 CBed 完整大小
};
class CHouse {
CBed* bed;
public:
void GoToBed() { bed->Sleep(); } // ✗ 編譯錯誤:CBed 尚未定義
};
解法:將實作移至 .cpp 檔,在已 #include "Bed.h" 後呼叫。
// House.h
class CHouse {
CBed* bed;
void RemoveBed() { delete bed; } // ✗ 錯誤:CBed 解構函式未知
};
若 CBed 有動態記憶體成員(如 int* num),其解構函式必須被呼叫以釋放資源。在前置宣告階段編譯器不知道解構函式細節,會導致資源洩漏或未定義行為。
解法:同陷阱 2,移至 .cpp 實作。
// A.h
class B; // 前置宣告
class A {
B* b; // 指標
public:
A(); ~A();
};
// A.cpp
#include "B.h"
#include "A.h"
A::A() : b(new B) {}
// B.h
class A; // 前置宣告
class B {
A a; // 這裡若用 A* 更安全,直接用 A 需在 B.cpp include A.h
public:
B(); ~B();
};
// B.cpp
#include "A.h"
#include "B.h"
B::B() : a() {} // 或 a = new A() 若為指標
當 CHouse 宣告 CBed::Sleep(CHouse&) 為友元,而 CBed::Sleep 參數需 CHouse 完整型別時,雙方互相需要對方定義。解法:
// House.h
class CBed; // 前置宣告
class CHouse {
friend void CBed::Sleep(CHouse&); // 友元宣告
public:
CHouse(); ~CHouse();
};
// Bed.h
class CHouse; // 前置宣告
class CBed {
public:
void Sleep(CHouse&); // 參數用參考,僅需前置宣告
};
| 情境 | 建議做法 |
|---|---|
| 類別成員為他類別 | 前置宣告 + 指標/參考 |
| 需呼叫他類別方法 | 在 .cpp 中 include 完整定義後實作 |
| 需刪除他類別物件 | 在 .cpp 中實作 delete |
| 友元函式參數為他類別 | 前置宣告 + 參考/指標參數 |
| 減少 header 相依 | 盡量用前置宣告,include 移至 .cpp |