1. ホーム
  2. c++

[解決済み] クラス間の循環的な依存関係によるビルドエラーを解決する

2022-03-21 10:20:59

質問

私はよく、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;
    }
    
    

解決方法は?

これを考える方法は、"コンパイラのように考えること"です。

あなたがコンパイラを書いていると想像してください。そして、このようなコードを見てください。

// 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 ファイルを作成します(このとき .cc であって .h がコンパイルの単位である場合)、オブジェクト 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;
}

これは 作品 . これは すごい . しかし、この時点で、循環参照問題と、その修正が悪いとはいえ、私たちが"fix"するために何をしたかについて、理解していただく必要があります。

この修正が悪いのは、次の人が #include "A.h" を宣言する必要があります。 B を使用することができず、ひどい #include というエラーが発生します。そこで、この宣言を A.h そのものです。

// file: A.h
class B;
class A {
  B* _b; // or any of the other variants.
};

そして、その中に B.h この時点では #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; 
}

HTH