58

私は C++ の初心者ですが、この (おそらく些細な) 質問に対する答えをオンラインで見つけることができませんでした。2 つのクラスが互いにインクルードしているコードのコンパイルに問題があります。まず、 #include ステートメントをマクロの内側または外側に配置する必要がありますか? 実際には、これは問題ではないようです。ただし、この特定のケースでは、問題が発生しています。#include ステートメントをマクロの外に置くと、コンパイラが再帰的に実行され、「#include ネストが深すぎます」というエラーが表示されます。#include が呼び出される前にどちらのクラスも完全に定義されていないため、これは私には理にかなっているようです。しかし、奇妙なことに、それらを中に入れようとすると、認識されないため、クラスの 1 つの型を宣言できません。本質的に、私がコンパイルしようとしているものは次のとおりです。

ああ

#ifndef A_H_
#define A_H_

#include "B.h"

class A
{
    private:
        B b;

    public:
        A() : b(*this) {}
};

#endif /*A_H_*/

Bh

#ifndef B_H_
#define B_H_

#include "A.h"

class B
{
    private:
            A& a;

    public:
        B(A& a) : a(a) {}
 };

#endif /*B_H_*/

main.cpp

#include "A.h"

int main()
{
    A a;
}

違いがある場合は、g++ 4.3.2 を使用しています。

明確にするために、一般的に、#include ステートメントはどこに行けばよいのでしょうか? 私は常にそれらがマクロの外に出ているのを見てきましたが、私が明確に説明したシナリオはこの原則を破っているようです. 事前にヘルパーに感謝します!ばかげた間違いを犯した場合は、私の意図を明確にすることを許可してください!

4

7 に答える 7

77

「マクロ」とは、 #ifndef にガードが含まれていることを意味していると思いますか? もしそうなら、 #includes は間違いなく中に入るはずです。これは、インクルード ガードが存在する主な理由の 1 つです。

とにかく、問題は、(他のクラス内で) A クラスと B クラスを使用する時点では、まだ宣言されていないことです。#includes が処理された後のコードを見てください。

//#include "A.h" start
#ifndef A_H_
#define A_H_

//#include "B.h" start
#ifndef B_H_
#define B_H_

//#include "A.h" start
#ifndef A_H_ // A_H_ is already defined, so the contents of the file are skipped at this point
#endif /*A_H_*/

//#include "A.h" end

class B
{
    private:
            A& a;

    public:
            B(A& a) : a(a) {}
 };

#endif /*B_H_*/

//#include "B.h" end

class A
{
    private:
            B b;

    public:
            A() : b(*this) {}
};

#endif /*A_H_*/
//#include "A.h" end

int main()
{
    A a;
}

コードを読んでみましょう。B は、コンパイラが検出する最初のクラスであり、A&メンバーが含まれています。とはA? コンパイラは の定義をAまだ検出していないため、エラーが発生します。

解決策は、A の前方宣言を行うことです。B の定義の前のある時点で、次の行を追加します。class A;

これにより、A がクラスであるという必要な情報がコンパイラに提供されます。それについてはまだ何もわかっていませんが、B はそれへの参照を含める必要があるだけなので、これで十分です。A の定義では、型 B のメンバー (参照ではない) が必要なので、ここでは B の定義全体が可視でなければなりません。幸いなことに、それはどちらですか。

于 2008-12-28T11:24:42.680 に答える
17

明確にするために、一般的に、#include ステートメントはどこに行けばよいのでしょうか?

あなたが言及した理由により、インクルードガードの内部。

他の問題については、次のように、少なくとも 1 つのクラスを前方宣言する必要があります。

#ifndef B_H_
#define B_H_

// Instead of this:
//#include "A.h"

class A;

class B
{
    private:
            A& a;

    public:
            B(A& a) : a(a) {}
 };

#endif /*B_H_*/

ただし、これは宣言に対してのみ機能します。のインスタンスを実際に使用Aするとすぐに、それも定義する必要があります。

ところで、Nathan の言うことは本当です。クラス インスタンスを相互に再帰的に配置することはできません。これは、インスタンスへのポインター(または、あなたの場合は参照) でのみ機能します。

于 2008-12-28T11:20:08.210 に答える
7

このような状況では、前方宣言を使用してすべてのソースに含まれる共通ヘッダーを作成します。

#ifndef common_hpp
#define common_hpp

class A;
class B;

#endif

次に、個々のクラス ヘッダー ファイルは通常、他のクラスへのポインタまたは参照のみが必要な場合、他のクラスを参照するための #include を必要としません。半分の時間で、これらのヘッダーには他の利点がありますが、少なくとも循環参照に関する問題は common.hpp で解決されます。

于 2008-12-30T21:03:46.580 に答える
4

おっとっと!#include ステートメントをクラス内に配置し、前方宣言を使用することを含む解決策を見つけたと思います。したがって、コードは次のようになります。

#ifndef A_H_
#define A_H_

class B;

#include "B.h"

class A
{
    private:
            B b;

    public:
            A() : b(*this) {}
};

#endif /*A_H_*/

クラスBについても同様です。コンパイルしますが、これが最善のアプローチですか?

于 2008-12-28T11:23:27.753 に答える
1

優れたソフトウェア設計における 2 つのクラス間の依存関係は、ツリーとして描くことができます。

このため、C++ では 2 つの .h ファイルが相互に #include されることはありません。

于 2014-11-18T03:24:45.717 に答える
0

これができるとは思えません。2 つの関数を相互に再帰的に呼び出すことについて話しているのではなく、2 つのオブジェクトを相互に再帰的に配置することについて話しているのです。家の写真と家の写真などを配置することを考えてみてください。無限の数の家と写真があるため、これは無限のスペースを占有します。

できることは、それぞれを持ち、相互へのポインターまたは参照のいずれかを含めることです。AB

于 2008-12-28T11:18:41.223 に答える
0

一部のコンパイラ (gcc を含む) も#pragma onceをサポートしていますが、質問の「ガードを含める」イディオムは通常の方法です。

于 2008-12-28T12:20:55.343 に答える