前置き宣言とC++ヘッダファイルの相互包含によるerror:'xxx'does not name a type問題


ソース・ファイルでクラスのポインタを宣言または定義する場合は、使用前にクラスを宣言または定義する必要があります.そのため、次のコードはエラーを報告します.
class A
{
public:
    B *b;
};

class B
{
public:
    A *a;
};

int main()
{
    return 0;
}

エラーが「error:‘B’does not name a type」となったのは、AクラスでB*bを使用する前にBクラスを宣言または定義していないためであり、最初の行に前置き宣言(forward declaration)「class B;」を付けると、このような問題はありません.ヘッダファイルが互いに含まれている場合、「error:『xxx』does not name a type」というエラーの原因は上のコードと同じです.次のコードを見てください.a.h:
#ifndef A_H_INCLUDED
#define A_H_INCLUDED

#include "b.h"

class A
{
public:
    B *b;
};

#endif // A_H_INCLUDED

b.h:
#ifndef B_H_INCLUDED
#define B_H_INCLUDED

#include "a.h"

class B
{
public:
    A *a;
};

#endif // B_H_INCLUDED

main.cpp:
#include "a.h"
#include "b.h"

int main()
{
    return 0;
}

コンパイルすると「error:『A』does not name a type」と間違えてしまいますが、なぜそうなるのでしょうか.a.hが前処理を経てどのように展開されるかを見てみましょう.前処理コマンドは「gcc-E-o a.i a.h」です.
# 1 "a.h"
# 1 ""
# 1 ""
# 1 "a.h"



# 1 "b.h" 1



# 1 "a.h" 1
# 5 "b.h" 2

class B
{
public:
    A *a;
};
# 5 "a.h" 2

class A
{
public:
    B *b;
};

「#」で始まる行を無視すると、クラスの順序が交換されただけで、エラーの原因は最初のソースファイルと同じであることがわかります.解決策も簡単で、a.hの「#include“b.h”」をBクラスの前置き宣言「class B;」に置き換え、b.hでも同様の修正を行います.これで問題にならない.もちろん、AクラスのメンバーはBクラスのポインタだけで、Bクラスの変数は持てないという前提があります.Aクラスヘッダファイルでは、Bクラスのメンバーまたはメンバー関数にアクセスできません.いずれの場合もAクラスはBクラスの大きさやその他の詳細を知る必要があり、前置き宣言ではこれらの詳細を提供できず、「error:field‘b’has incomplete type‘B’」のような問題が発生する.