別のクラスのヘッダー ファイルでクラスの前方宣言を行うことが許可されている場合の定義を探しています。
基本クラス、メンバーとして保持されているクラス、参照によってメンバー関数に渡されるクラスなどに対してそれを行うことはできますか?
別のクラスのヘッダー ファイルでクラスの前方宣言を行うことが許可されている場合の定義を探しています。
基本クラス、メンバーとして保持されているクラス、参照によってメンバー関数に渡されるクラスなどに対してそれを行うことはできますか?
コンパイラーの立場に立ちましょう。型を前方宣言すると、コンパイラーはこの型が存在することだけを認識します。そのサイズ、メンバー、またはメソッドについては何も知りません。これが不完全型と呼ばれる理由です。したがって、コンパイラは型のレイアウトを知る必要があるため、型を使用してメンバーまたは基本クラスを宣言することはできません。
次の前方宣言を想定しています。
class X;
できることとできないことを次に示します。
不完全型でできること:
メンバーが不完全な型へのポインターまたは参照であることを宣言します。
class Foo {
X *p;
X &r;
};
不完全な型を受け入れる/返す関数またはメソッドを宣言します。
void f1(X);
X f2();
不完全な型へのポインター/参照を受け入れる/返す関数またはメソッドを定義します (ただし、そのメンバーは使用しません)。
void f3(X*, X&) {}
X& f4() {}
X* f5() {}
不完全型でできないこと:
基底クラスとして使用する
class Foo : X {} // compiler error!
これを使用してメンバーを宣言します。
class Foo {
X m; // compiler error!
};
この型を使用して関数またはメソッドを定義する
void f1(X x) {} // compiler error!
X f2() {} // compiler error!
そのメソッドまたはフィールドを使用して、実際には不完全な型の変数を逆参照しようとしています
class Foo {
X *m;
void method()
{
m->someMethod(); // compiler error!
int i = m->someField; // compiler error!
}
};
テンプレートに関しては、絶対的なルールはありません。不完全な型をテンプレート パラメーターとして使用できるかどうかは、テンプレートでの型の使用方法に依存します。
たとえば、std::vector<T>
はパラメータが完全な型である必要がありますが、そうではありboost::container::vector<T>
ません。特定のメンバー関数を使用する場合にのみ、完全な型が必要になることがあります。これは、std::unique_ptr<T>
たとえば の場合です。
十分に文書化されたテンプレートは、完全な型である必要があるかどうかを含め、パラメーターのすべての要件をドキュメントに示す必要があります。
主なルールは、前方宣言するファイルでメモリ レイアウト (およびメンバー関数とデータ メンバー) を知る必要がないクラスのみを前方宣言できるということです。
これにより、基本クラスと、参照とポインターを介して使用されるクラス以外は除外されます。
不完全な型へのポインタや参照だけでなく、不完全な型であるパラメータや戻り値を指定する関数プロトタイプを宣言することもできます。ただし、ポインターまたは参照でない限り、パラメーターまたは戻りタイプが不完全な関数を定義することはできません。
例:
struct X; // Forward declaration of X
void f1(X* px) {} // Legal: can always use a pointer
void f2(X& x) {} // Legal: can always use a reference
X f3(int); // Legal: return value in function prototype
void f4(X); // Legal: parameter in function prototype
void f5(X) {} // ILLEGAL: *definitions* require complete types
これまでのところ、クラス テンプレートの前方宣言をいつ使用できるかについて説明している回答はありません。それで、ここに行きます。
クラス テンプレートは、次のように宣言して転送できます。
template <typename> struct X;
受け入れられた回答の構造に従って、
できることとできないことを次に示します。
不完全型でできること:
別のクラス テンプレートの不完全な型へのポインターまたは参照としてメンバーを宣言します。
template <typename T>
class Foo {
X<T>* ptr;
X<T>& ref;
};
メンバーが、その不完全なインスタンス化の 1 つへのポインターまたは参照であることを宣言します。
class Foo {
X<int>* ptr;
X<int>& ref;
};
不完全な型を受け入れる/返す関数テンプレートまたはメンバー関数テンプレートを宣言します。
template <typename T>
void f1(X<T>);
template <typename T>
X<T> f2();
不完全なインスタンス化の 1 つを受け入れる/返す関数またはメンバー関数を宣言します。
void f1(X<int>);
X<int> f2();
不完全な型へのポインター/参照を受け入れる/返す関数テンプレートまたはメンバー関数テンプレートを定義します (ただし、そのメンバーは使用しません)。
template <typename T>
void f3(X<T>*, X<T>&) {}
template <typename T>
X<T>& f4(X<T>& in) { return in; }
template <typename T>
X<T>* f5(X<T>* in) { return in; }
不完全なインスタンス化の 1 つへのポインター/参照を受け入れる/返す関数またはメソッドを定義します (ただし、そのメンバーは使用しません)。
void f3(X<int>*, X<int>&) {}
X<int>& f4(X<int>& in) { return in; }
X<int>* f5(X<int>* in) { return in; }
別のテンプレート クラスの基本クラスとして使用する
template <typename T>
class Foo : X<T> {} // OK as long as X is defined before
// Foo is instantiated.
Foo<int> a1; // Compiler error.
template <typename T> struct X {};
Foo<int> a2; // OK since X is now defined.
これを使用して、別のクラス テンプレートのメンバーを宣言します。
template <typename T>
class Foo {
X<T> m; // OK as long as X is defined before
// Foo is instantiated.
};
Foo<int> a1; // Compiler error.
template <typename T> struct X {};
Foo<int> a2; // OK since X is now defined.
この型を使用して関数テンプレートまたはメソッドを定義する
template <typename T>
void f1(X<T> x) {} // OK if X is defined before calling f1
template <typename T>
X<T> f2(){return X<T>(); } // OK if X is defined before calling f2
void test1()
{
f1(X<int>()); // Compiler error
f2<int>(); // Compiler error
}
template <typename T> struct X {};
void test2()
{
f1(X<int>()); // OK since X is defined now
f2<int>(); // OK since X is defined now
}
不完全型でできないこと:
そのインスタンス化の 1 つを基本クラスとして使用する
class Foo : X<int> {} // compiler error!
そのインスタンス化の 1 つを使用してメンバーを宣言します。
class Foo {
X<int> m; // compiler error!
};
インスタンス化の 1 つを使用して関数またはメソッドを定義する
void f1(X<int> x) {} // compiler error!
X<int> f2() {return X<int>(); } // compiler error!
そのインスタンス化の 1 つのメソッドまたはフィールドを使用して、実際には不完全な型の変数を逆参照しようとしています
class Foo {
X<int>* m;
void method()
{
m->someMethod(); // compiler error!
int i = m->someField; // compiler error!
}
};
クラス テンプレートの明示的なインスタンス化を作成する
template struct X<int>;
クラスへのポインターまたは参照のみを使用するファイルでは、それらのポインター/参照を考慮してメンバー/メンバー関数を呼び出す必要はありません。
class Foo;
//前方宣言あり
Foo* または Foo& 型のデータ メンバーを宣言できます。
Foo 型の引数および/または戻り値を持つ関数を宣言できます (定義はできません)。
Foo 型の静的データ メンバーを宣言できます。これは、静的データ メンバーがクラス定義の外で定義されているためです。
合法性の理由ではなく、堅牢なソフトウェアと誤解の危険性のために、Luc Tourailleの回答に同意しないため、これを単なるコメントではなく別の回答として書いています。
具体的には、インターフェースのユーザーが知っておく必要があると期待する暗黙の契約に問題があります。
参照型を返すか受け入れる場合は、ポインタまたは参照を通過できると言っているだけです。これらは、前方宣言によってのみ認識されている可能性があります。
不完全な型を返す場合、呼び出し元は X の完全な型仕様を持っている必要X f2();
があると言っています。呼び出しサイトで LHS または一時オブジェクトを作成するために必要です。
同様に、不完全な型を受け入れる場合、呼び出し元はパラメーターであるオブジェクトを作成する必要があります。そのオブジェクトが関数から別の不完全な型として返された場合でも、呼び出しサイトには完全な宣言が必要です。すなわち:
class X; // forward for two legal declarations
X returnsX();
void XAcceptor(X);
XAcepptor( returnsX() ); // X declaration needs to be known here
依存関係が他のヘッダーを必要とせずに、ヘッダーがそれを使用するのに十分な情報を提供する必要があるという重要な原則があると思います。つまり、ヘッダーが宣言する関数を使用するときに、コンパイラ エラーを発生させることなく、ヘッダーをコンパイル ユニットに含めることができる必要があります。
を除外する
この外部依存関係が望ましい動作である場合。条件付きコンパイルを使用する代わりに、X を宣言する独自のヘッダーを提供するように十分に文書化された要件を持つことができます。これは #ifdefs を使用する代わりであり、モックやその他のバリアントを導入する便利な方法です。
重要な違いは、インスタンス化することが明示的に期待されていないいくつかのテンプレート テクニックです。
定義 (ポインターと参照を考えてください) が必要ない限り、前方宣言を回避できます。これが、実装ファイルが通常、適切な定義のヘッダーをプルするのに対し、ほとんどの場合ヘッダーに表示される理由です。
私が従う一般的なルールは、必要な場合を除き、ヘッダー ファイルをインクルードしないことです。したがって、クラスのオブジェクトをクラスのメンバー変数として格納しない限り、それを含めません。前方宣言のみを使用します。
クラスのメンバーとして他の型 (クラス) を使用する場合は、通常、クラス ヘッダー ファイルで前方宣言を使用する必要があります。C++ はその時点でそのクラスの定義をまだ認識していないため、ヘッダー ファイルで前方宣言されたクラスメソッドを使用することはできません。これは .cpp ファイルに移動する必要があるロジックですが、テンプレート関数を使用している場合は、テンプレートを使用する部分だけに減らし、その関数をヘッダーに移動する必要があります。
前方宣言によってコードがコンパイルされます (obj が作成されます)。ただし、定義が見つからない限り、リンク (exe の作成) は成功しません。