別のクラスのヘッダーファイルでクラスの前方宣言をいつ許可するかの定義を探しています。
基本クラス、メンバーとして保持されているクラス、参照によってメンバー関数に渡されたクラスなどでそれを行うことはできますか?
別のクラスのヘッダーファイルでクラスの前方宣言をいつ許可するかの定義を探しています。
基本クラス、メンバーとして保持されているクラス、参照によってメンバー関数に渡されたクラスなどでそれを行うことはできますか?
回答:
コンパイラーの立場に身を置く:型を転送宣言すると、コンパイラーはすべて、この型が存在することを認識します。サイズ、メンバー、メソッドについては何も知りません。これが不完全型と呼ばれる理由です。したがって、コンパイラーは型のレイアウトを知っている必要があるため、型を使用してメンバーまたは基本クラスを宣言することはできません。
次の前方宣言を想定しています。
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>;
X
し、X<int>
あなたの答えの額のすべてが、1行だけのリュックのを服用すると、任意の実質的な方法で正確に同じであり、唯一、前方宣言の構文が異なるとs/X/X<int>/g
?それは本当に必要ですか?それとも私は違う小さな細部を見逃しましたか?可能ですが、視覚的に比較したところ、何も表示されません...
私はこれを単なるコメントではなく別の回答として書いています。なぜなら、合法性の理由ではなく、堅牢なソフトウェアと誤解の危険性があるため、Luc Tourailleの回答に同意しないからです。
具体的には、インターフェイスのユーザーが知っておくべきことの暗黙の契約に問題があります。
参照型を返すか受け入れる場合は、それらが前方宣言を通じてのみ既知である可能性があるポインタまたは参照を通過できることを示しているだけです。
不完全な型を返すX f2();
場合は、呼び出し元がXの完全な型指定を持っている必要があることを意味します。呼び出しサイトでLHSまたは一時オブジェクトを作成するために、Xが必要です。
同様に、不完全な型を受け入れる場合、呼び出し元はパラメーターであるオブジェクトを構築している必要があります。そのオブジェクトが関数から別の不完全な型として返された場合でも、呼び出しサイトには完全な宣言が必要です。つまり:
class X; // forward for two legal declarations
X returnsX();
void XAcceptor(X);
XAcepptor( returnsX() ); // X declaration needs to be known here
ヘッダーには、他のヘッダーを必要としない依存関係なしに、ヘッダーを使用するのに十分な情報を提供するという重要な原則があると思います。つまり、ヘッダーは、宣言する関数を使用するときにコンパイラエラーを発生させることなく、コンパイルユニットに含めることができる必要があります。
以外
この外部依存関係が望ましい動作である場合。条件付きコンパイルを使用する代わりに、Xを宣言する独自のヘッダーを提供するように文書化された要件を持つことができます。これは、#ifdefsを使用する代わりに使用でき、モックやその他のバリアントを導入するのに便利な方法です。
重要な違いは、それらをインスタンス化することが明示的に期待されていないいくつかのテンプレートテクニックであり、誰かが私にだまされないように述べられています。
I disagree with Luc Touraille's answer
そのため、長さが必要な場合は、ブログ投稿へのリンクを含めてコメントを書いてください。これは尋ねられた質問に答えません。Xがどのように機能するかについての質問を正当化した人全員が、Xを使用することに同意しない、またはXを使用する自由を制限する必要がある制限について議論することで答えを正当化します。
私が従う一般的な規則は、必要がない限りヘッダーファイルを含めないことです。したがって、クラスのオブジェクトをクラスのメンバー変数として格納している場合を除いて、そのオブジェクトは含めません。フォワード宣言を使用します。
他の型(クラス)をクラスのメンバーとして使用する場合は、通常、クラスヘッダーファイルで前方宣言を使用します。C ++はその時点ではまだそのクラスの定義を認識していないため、ヘッダーファイルで前方宣言されたクラスメソッドを使用することはできません。これは.cpp-filesに移動する必要があるロジックですが、テンプレート関数を使用している場合は、テンプレートを使用する部分のみに減らし、その関数をヘッダーに移動する必要があります。
前方宣言がコードをコンパイルする(objが作成される)ことを理解してください。ただし、定義が見つからない場合、リンク(exeの作成)は成功しません。
class A; class B { A a; }; int main(){}
、そしてそれがどうなるかを私に知らせてください。もちろん、コンパイルされません。ここでの適切な答えはすべて、前方宣言が有効である理由と、限定された正確なコンテキストを説明しています。代わりに、まったく異なる何かについてこれを書いた。
Luc Tourailleの回答に記載されていない転送クラスで実行できる重要なことを1つだけ追加したいと思います。
不完全なタイプでできること:
不完全な型へのポインタ/参照を受け入れる/返す関数またはメソッドを定義し、そのポインタ/参照を別の関数に転送します。
void f6(X*) {}
void f7(X&) {}
void f8(X* x_ptr, X& x_ref) { f6(x_ptr); f7(x_ref); }
モジュールは、前方宣言されたクラスのオブジェクトを別のモジュールに渡すことができます。
すでに、Luc Tourailleは、クラスの前方宣言を使用する場合と使用しない場合で、非常によく説明しています。
私はそれを使用する必要がある理由をそれに追加します。
不要な依存性注入を回避するために、可能な限りForward宣言を使用する必要があります。
#include
ヘッダファイルは、したがって、複数のファイルに追加され、我々は別のヘッダファイルにヘッダを追加する場合、それは追加することによって回避することができるソースコードの様々な部分に不要な依存性注入を追加する#include
にヘッダを.cpp
ファイル可能な限りではなく、別のヘッダファイルに追加することとヘッダー.h
ファイルで可能な限りクラス転送宣言を使用します。