C ++テンプレートで不完全な型(前方宣言)を回避できるのはなぜですか?
-
05-07-2019 - |
質問
次の簡単なプログラムを3回繰り返してみました。これは、コンテナとイテレータのペアのクラスを記述する非常に単純化された試みですが、不完全な型(前方宣言)の問題に直面していました。すべてをテンプレート化すると、これが実際に可能であることがわかりました。ただし、実際にテンプレートパラメータを使用した場合のみです。 ( Googleスパース可能コード。)
2番目が動作し、3番目が動作しない理由を説明するヒントはありますか? (最初の1つが機能しない理由を知っています-コンパイラはコンテナのメモリレイアウトを知る必要があります。)
事前に感謝します。
// This doesn't work: invalid use of incomplete type.
#if 0
struct container;
struct iter {
container &c;
int *p;
iter(container &c) : c(c), p(&c.value()) {}
};
struct container {
int x;
int &value() { return x; }
iter begin() { return iter(*this); }
};
int main() {
container c;
c.begin();
return 0;
}
#endif
// This *does* work.
template<typename T> struct container;
template<typename T> struct iter {
container<T> &c;
T *p;
iter(container<T> &c) : c(c), p(&c.value()) {}
};
template<typename T> struct container {
T x;
T &value() { return x; }
iter<T> begin() { return iter<T>(*this); }
};
int main() {
container<int> c;
c.begin();
return 0;
};
// This doesn't work either.
#if 0
template<typename T> struct container;
template<typename T> struct iter {
container<int> &c;
int *p;
iter(container<int> &c) : c(c), p(&c.value()) {}
};
template<typename T> struct container {
int x;
int &value() { return x; }
iter<int> begin() { return iter<int>(*this); }
};
int main() {
container<int> c;
c.begin();
return 0;
}
#endif
解決
コピー操作を実行しているため、最初は container
の定義が必要です。 container
の定義の後に iter
のコンストラクタを定義すれば大丈夫です。だから:
struct container;
struct iter {
container &c;
int *p;
iter(container &c);
};
struct container {
int x;
int &value() { return x; }
iter begin() { return iter(*this); }
};
iter::iter(container &c) : c(c), p(&c.value()) {}
int main() {
container c;
c.begin();
return 0;
}
2番目の例は、 main
関数で実際にクラスをインスタンス化するまでクラスがないため、機能します。その時までに、すべてのタイプが定義されます。 mainの後に iter
または container
テンプレート定義のいずれかを移動してみてください。エラーが発生します。
3番目の例は、 int
に特化したものです。 iter
のテンプレートパラメータは使用されないため、これはコンパイルされます。特殊化構文は少しずれています。ただし、適切なコンストラクターがないため、 x
のガーベッジを取得するだけです。さらに、反復子はポインターによって適切にモデル化されます。 this
の値を渡すことはあまり役に立ちません。通常、イテレータは、個々のオブジェクトではなくシーケンスに必要です。ただし、構築を妨げるものは何もありません。
そして、関数本体の後に;
は必要ありません。
他のヒント
コンテナの定義後にiter :: iter()を定義することにより、テンプレートなしでこれを行うことができます:
struct container;
struct iter {
container &c;
int *p;
iter(container &c);
};
struct container {
int x;
int &value() { return x; }
iter begin() { return iter(*this); }
};
iter::iter(container &c)
: c(c), p(&c.value()) {}
int main() {
container c;
c.begin();
return 0;
}
テンプレートをインスタンス化すると、両方のクラスが完全に定義されるため、テンプレートバージョンは機能します。
最初のケースでは、クラスが定義される前にContainerクラスのメンバー関数にアクセスしようとしているため、これは機能しません。
2番目の場合、テンプレートは特定のタイプで最初に使用されるときにインスタンス化されます。その時点で、メインでContainerクラスが定義されているため、コンパイルされます。
3番目のケースでは、循環参照があります。コンテナはiterを使用し、iterはコンテナを使用するため、機能しません。