有時候在設(shè)計數(shù)據(jù)結(jié)構(gòu)的時候,可能會遇到兩個類需要相互引用的情形。比如類 A 有類型為 B 的成員,而類 B 又有類型為 A 的成員。 那么這種情形下,兩個類的設(shè)計上需要注意什么呢? 同一文件嘗試方案將 A 和 B 的定義都放在一個文件中,例如: #include <iostream>class A { public: A() { aa_ = 'A'; } char aa_; B b_; };class B { public: B() { bb_ = 'B'; } char bb_; A a_; };int main() { return 0; } 編譯這一段代碼,編譯出錯: yngzmiao@yngzmiao-virtual-machine:~/test$ g++ main.cpp -o main --std=c++11main.cpp:9:5: error: 'B’ does not name a type B b_; ^ 編譯的報錯提示,B 不是一個數(shù)據(jù)類型??赡苣銜?,會不會 #include <iostream>class B;class A { public: A() { aa_ = 'A'; } char aa_; B b_; };class B { public: B() { bb_ = 'B'; } char bb_; A a_; };int main() { return 0; } 編譯這一段代碼,編譯仍然出錯: yngzmiao@yngzmiao-virtual-machine:~/test$ g++ main.cpp -o main --std=c++11main.cpp:11:7: error: field 'b_’ has incomplete type B b_; ^ 編譯時出現(xiàn) 'field has incomplete type',通常的錯誤原因?yàn)椋?strong>類或結(jié)構(gòu)體的前向聲明只能用來定義指針對象或引用,因?yàn)榫幾g到這里時還沒有發(fā)現(xiàn)定義,不知道該類或者結(jié)構(gòu)的內(nèi)部成員,沒有辦法具體的構(gòu)造一個對象,所以會報錯。 解決辦法:將類成員改成指針就好了。程序中使用 incomplete type 實(shí)現(xiàn)前置聲明,有助與實(shí)現(xiàn)數(shù)據(jù)類型細(xì)節(jié)的隱藏。 按照這個辦法來進(jìn)行修改,將 b_的類型由 B 的對象修改成指向類型 B 的指針: #include <iostream>class B;class A { public: A() { aa_ = 'A'; } char aa_; B *b_; };class B { public: B() { bb_ = 'B'; } char bb_; A a_; };int main() { A tmp1; std::cout << tmp1.aa_ << ' ' << tmp1.b_->bb_ << std::endl; B tmp2; std::cout << tmp2.bb_ << ' ' << tmp2.a_.aa_ << std::endl; return 0; } 編譯這一段代碼,編譯順利,沒有問題。運(yùn)行這段代碼: yngzmiao@yngzmiao-virtual-machine:~/test$ g++ main.cpp -o main --std=c++11yngzmiao@yngzmiao-virtual-machine:~/test$ ./main A H B A 又有問題了,tmp1.b_->bb_的打印結(jié)果為 H。這個問題很容易檢查出問題:在類 A 的定義中,定義了指向類型 B 的指針 b_,但是并沒有對該指針分配內(nèi)存空間,當(dāng)然會有一些奇怪的值打印出來。可以修改為: #include <iostream>class B;class A { public: A() { aa_ = 'A'; b_ = new B(); } char aa_; B *b_; };class B { public: B() { bb_ = 'B'; } char bb_; A a_; };int main() { A tmp1; std::cout << tmp1.aa_ << ' ' << tmp1.b_->bb_ << std::endl; B tmp2; std::cout << tmp2.bb_ << ' ' << tmp2.a_.aa_ << std::endl; return 0; } 編譯又出現(xiàn)問題了: yngzmiao@yngzmiao-virtual-machine:~/test$ g++ main.cpp -o main --std=c++11main.cpp: In constructor 'A::A()’: main.cpp:9:18: error: invalid use of incomplete type 'class B’ b_ = new B(); ^ main.cpp:3:7: error: forward declaration of 'class B’ class B; ^ 編譯錯誤的原因還是 incomplete type,即類型 B 的結(jié)構(gòu)還不知道,怎么能 new 出來呢? 最終代碼如果想要獲得正確的代碼,不能將 new 的操作放在構(gòu)造函數(shù)中,放在其他地方手動創(chuàng)建即可: #include <iostream>class B;class A { public: A() { aa_ = 'A'; } char aa_; B *b_; };class B { public: B() { bb_ = 'B'; } char bb_; A a_; };int main() { A tmp1; tmp1.b_ = new B(); std::cout << tmp1.aa_ << ' ' << tmp1.b_->bb_ << std::endl; B tmp2; std::cout << tmp2.bb_ << ' ' << tmp2.a_.aa_ << std::endl; return 0; } 編譯并運(yùn)行這段代碼: yngzmiao@yngzmiao-virtual-machine:~/test$ g++ main.cpp -o main --std=c++11yngzmiao@yngzmiao-virtual-machine:~/test$ ./main A B B A 原因分析類 A 和類 B 相互引用比較麻煩的 不同文件不同文件指的是:類型 A 定義在 A.h 文件,類型 B 定義在 B.h 文件,同時在 main.cpp 中創(chuàng)建 A、B 類型的對象進(jìn)行輸出。通過上文的一些經(jīng)驗(yàn),可能還需要 create() 函數(shù)來對 B 指針進(jìn)行 new 操作。 由于不同文件的寫法,坑比較多,接下來就直接給出正確的代碼內(nèi)容。 在 A.h 文件中,定義類 A: #ifndef A_H#define A_Hclass B; // highlight 1class A { public: A() { aa_ = 'A'; } void create(); char aa_; B *b_; };#endif 在 A.cpp 文件中,定義類 A 方法的實(shí)現(xiàn): #include 'B.h' // highlight 2void A::create() { b_ = new B(); } 在 B.h 文件中,定義類 B: #ifndef B_H#define B_H#include 'A.h' // highlight 3class B { public: B() { bb_ = 'B'; } char bb_; A a_; };#endif 最終在 main.cpp 文件中,使用類 A 和類 B: #include 'B.h'#include <iostream>int main() { A tmp1; tmp1.create(); std::cout << tmp1.aa_ << ' ' << tmp1.b_->bb_ << std::endl; B tmp2; std::cout << tmp2.bb_ << ' ' << tmp2.a_.aa_ << std::endl; return 0; } 對這一段代碼中的坑進(jìn)行羅列:
總結(jié)兩個類相互引用,一個用對象、include;另一個用指針、前置聲明、create 手動 new。手動 new 的過程不能在構(gòu)造函數(shù)中進(jìn)行,同時需要知道另一個類的完整定義(include)。 注意:本文所舉例的部分都沒有對 new 出來的空間進(jìn)行 delete 操作,會引發(fā)內(nèi)存泄漏。這部分需要讀者自行補(bǔ)充。 |
|