読者です 読者をやめる 読者になる 読者になる

Flat Leon Works

アプリやゲームを作ってます。

【C++】クラスの前方宣言いろいろ

クラスの前方宣言とは

クラスの前方宣言は、クラスが定義される前にそのクラス名を利用するためのものです。先行宣言とも呼ばれます。

class クラス名;で前方宣言になります。

class B; // 前方宣言

class A
{
public:
    void func( B* p ); // Bの定義がまだ出てないけど、前方宣言があるのでエラーにならない
};

クラスの前方宣言の用途

クラスの前方宣言は主に以下の目的で利用されます。

  • ヘッダーでの別のヘッダのインクルードを減らす
  • 相互参照を実現する

ヘッダーでの別のヘッダのインクルードを減らす

クラスの前方宣言を利用すると、ヘッダーでの別のヘッダーのインクルードを減らすことができます。ヘッダーでの別のヘッダーのインクルードは、意図しないインクルードや再び自分をインクルードしてしまう循環インクルードを発生させ、よくわからないコンパイルエラーになってしまいます。

また、無駄なヘッダーのインクルードを減らすことはコンパイル時間の削減につながります。

相互参照を実現する

相互参照とは2つのクラスの定義に互いのクラス名が出現していることです。相互参照が発生している状況で普通にインクルードをしてしまうと、循環インクルードが発生し、結果的に正しくクラス定義が読み込めずコンパイルエラーになってしまいます。

【インクルードだと相互参照を実現できない】

// A.h
#include "B.h"
class A
{
public:
    B* m_pB;
};

// B.h
#include "A.h"
class B
{
public:
    A* m_pA;
};

【クラスの前方宣言だと相互参照を実現できる】

// A.h
// #include "B.h"
class B; // 前方宣言
class A
{
public:
    B* m_pB;
};

// B.h
// #include "A.h"
class A; // 前方宣言
class B
{
public:
    A* m_pA;
};

クラスの前方宣言とポインタ変数の宣言を同時に行う

クラスの前方宣言とポインタ変数の宣言は同時に行うことができます。

class A
{
public:
    class B* m_pB; // クラスの前方宣言と変数宣言を同時に行う
};

クラス内でそのクラスの内部クラスの前方宣言

クラススコープ内で前方宣言を行うと、そのクラスの内部クラスの前方宣言になります。ただし、ポインタ変数の宣言と同時に行った場合は、内部クラスではなく、外部クラスの前方宣言になることに注意が必要です。

class A
{
public:
    class AInner;
    AInner* m_pAInner;
    // 変数宣言と同時に行った場合は内部クラスではなく外部クラスの前方宣言になる
    class B* m_pB;
};

class A::AInner
{
public:
};

内部クラスの前方宣言にはクラスの定義が必要

内部クラスの前方宣言には、そのクラスの外部クラスの定義が必要です。

class A;
class A::AInner; // Aの定義がないので前方宣言できない

このような仕様があるので、内部クラスは少し扱いにくいです。

テンプレートクラスの前方宣言

テンプレートクラスも前方宣言することができます。

template<class T> class A;
A<int>* pA;

名前空間内のクラスの前方宣言

特定の名前空間内にあるクラスを前方宣言することもできます。

名前空間n内にあるクラスAを前方宣言する場合】

/* これはだめ
class n::A;
*/
namespace n {
class A;
}

前方宣言ではなくインクルードを行う場面

  • 継承
  • inline関数などで定義が必要な場合

継承の場合はそのクラスの定義が必要なのでインクルードが必要になります。inline関数などでそのクラスのメンバにアクセスする場合もクラスの定義が必要になるのでインクルードが必要になります。しかしinline関数の実装のためにインクルードするのはなるべく避けましょう。

【おまけ】なぜ'前方宣言'と呼ぶのか

前方で宣言するから前方宣言なのではなく、前方にあることを宣言するから前方宣言と呼ぶらしいです。ここでいう前方というのは、ソースコードでいうと下を表します。前方宣言は英語ではForward declarationなので、Previous declarationという意味ではないということですね。

まとめ

前方宣言を使うと、ヘッダーの複雑なインクルードによる変なコンパイルエラーを減らし、コンパイル時間も削減することができます。 どんどん使っていきましょう。