私は、C++ プロジェクトで、いくつかの不適切な設計決定 (他の誰かが行った :) ) が原因で、複数のコンパイル/リンカー エラーに直面する状況によく遭遇します。このため、異なるヘッダー ファイル内の C++ クラス間の循環依存関係が発生します(同じファイル内でも発生する可能性があります)。しかし、幸いなことに (?)、この問題はそれほど頻繁には発生しないため、次に同じ問題が発生したときのために、この問題の解決策を思い出すことができます。
そこで、将来簡単に思い出せるように、代表的な問題とその解決策を投稿します。もちろん、より良い解決策があれば歓迎します。
A.h
class B; class A { int _val; B *_b; public: A(int val) :_val(val) { } void SetB(B *b) { _b = b; _b->Print(); // COMPILER ERROR: C2027: use of undefined type 'B' } void Print() { cout<<"Type:A val="<<_val<<endl; } };
B.h
#include "A.h" class B { double _val; A* _a; public: B(double val) :_val(val) { } void SetA(A *a) { _a = a; _a->Print(); } void Print() { cout<<"Type:B val="<<_val<<endl; } };
main.cpp
#include "B.h" #include <iostream> int main(int argc, char* argv[]) { A a(10); B b(3.14); a.Print(); a.SetB(&b); b.Print(); b.SetA(&a); return 0; }
ベストアンサー1
これについて考える方法は、「コンパイラのように考える」ことです。
コンパイラを書いているところを想像してください。そして、次のようなコードを見たとします。
// file: A.h
class A {
B _b;
};
// file: B.h
class B {
A _a;
};
// file main.cc
#include "A.h"
#include "B.h"
int main(...) {
A a;
}
.ccファイルをコンパイルする場合(.h ではなく .cc がコンパイル単位であることに注意してください)、オブジェクトにスペースを割り当てる必要があります。では、スペースはどれくらいでしょうか? を格納するのに十分な大きさですか? では、のサイズはどれくらいでしょうか? を格納するのに十分な大きさです! おっと。A
B
B
A
明らかに循環参照なので、これを解消する必要があります。
代わりに、コンパイラが事前に知っている限りのスペースを予約できるようにすることで、この制約を破ることができます。たとえば、ポインタと参照は常に 32 ビットまたは 64 ビット (アーキテクチャによって異なります) であるため、(どちらかを) ポインタまたは参照に置き換えると、状況は良くなります。次のように置き換えてみましょうA
。
// file: A.h
class A {
// both these are fine, so are various const versions of the same.
B& _b_ref;
B* _b_ptr;
};
今は状況は良くなりました。多少は。main()
それでもこう言います。
// file: main.cc
#include "A.h" // <-- Houston, we have a problem
#include
は、あらゆる範囲と目的において (プリプロセッサを取り除けば) ファイルを.ccにコピーするだけです。つまり、実際には.cc は次のようになります。
// file: partially_pre_processed_main.cc
class A {
B& _b_ref;
B* _b_ptr;
};
#include "B.h"
int main (...) {
A a;
}
コンパイラがこれを処理できない理由はおわかりでしょう。コンパイラには何が何だかわかりませんB
。これまでそのシンボルを見たことがないからです。
そこでコンパイラに について伝えましょうB
。これは と呼ばれます。前方宣言、そしてさらに詳しくはこの答え。
// main.cc
class B;
#include "A.h"
#include "B.h"
int main (...) {
A a;
}
これは動作します。素晴らしいものではありません。しかし、この時点で、循環参照の問題と、たとえ修正が不十分であったとしても、それを「修正」するために私たちが行ったことを理解しているはずです。
この修正がうまくいかない理由は、次の人がそれを使用する前に#include "A.h"
宣言する必要がありB
、ひどい#include
エラーが発生するからです。そこで、宣言をAh自体に移動しましょう。
// file: A.h
class B;
class A {
B* _b; // or any of the other variants.
};
そして、 Bhでは、この時点で、直接実行できます#include "A.h"
。
// file: B.h
#include "A.h"
class B {
// note that this is cool because the compiler knows by this time
// how much space A will need.
A _a;
}
ありがとう。