web-dev-qa-db-ja.com

C ++では、クラスを別のクラスから継承するものとして前方宣言することは可能ですか?

私ができることを知っています:

_class Foo;
_

しかし、次のようにクラスを別のクラスから継承していると宣言することはできますか?

_class Bar {};

class Foo: public Bar;
_

ユースケースの例は、共変参照戻り型です。

_// somewhere.h
class RA {}
class RB : public RA {}
_

...そして、somewhere.hを含まない別のヘッダーで

_// other.h
class RA;

class A {
 public:
  virtual RA* Foo();  // this only needs the forward deceleration
}

class RB : public RA; // invalid but...

class B {
 public:
  virtual RB* Foo();  // 
}
_

コンパイラがRB* B:Foo()の宣言を処理する必要がある唯一の情報shouldは、RBRAパブリックベースクラスとして。 Fooからの戻り値の逆参照を行う場合は、明らかにsomewhere.hが必要になります。ただし、一部のクライアントがFooを呼び出さない場合、コンパイルを大幅に高速化する可能性のあるsomewhere.hをインクルードする理由はありません。

59
anon

前方宣言は、その名前のクラスが存在し、他の場所で宣言および定義されることをコンパイラーに伝えるためにのみ本当に役立ちます。コンパイラがクラスに関するコンテキスト情報を必要とする場合には使用できません。また、コンパイラがクラスについて少しだけ伝えるために使用することもできません。 (一般に、他のコンテキストなしでそのクラスを参照する場合、たとえばパラメーターまたは戻り値としてのみ、前方宣言を使用できます。)

したがって、Fooの宣言に使用するシナリオでBarを前方宣言することはできません。また、基本クラスを含む前方宣言を持つことは意味がありません。何も?

40
Joe

前方宣言は、定義ではなく宣言です。そのため、クラスの宣言を必要とするもの(そのクラスへのポインターなど)には、前方宣言のみが必要です。ただし、定義を必要とするもの、つまりクラスの実際の構造を知る必要があるものは、前方宣言だけでは機能しません。

派生クラスは、親が存在することだけでなく、親の構造を確実に知る必要があるため、前方宣言では不十分です。

39

いいえ、ポインタだけを扱っている場合でも、継承宣言を転送することはできません。ポインター間の変換を処理する場合、コンパイラーはクラスの詳細を認識して変換を正しく行う必要がある場合があります。これは、多重継承の場合です。 (単一の継承のみを使用する階層の一部の部分を特殊なケースにすることもできますが、それは言語の一部ではありません。)

次の些細なケースを考えてみましょう。

#include <stdio.h>
class A { int x; };
class B { int y; };
class C: public A, public B { int z; };
void main()
{ 
    C c; A *pa = &c; B *pb = &c; C *pc = &c; 
    printf("A: %p, B: %p, C: %p\n", pa, pb, pc);
}

私が受け取った出力(32ビットVisual Studio 2010を使用)は、次のとおりです。

A: 0018F748, B: 0018F74C, C: 0018F748

そのため、多重継承の場合、関連するポインター間で変換するとき、コンパイラーはポインターの算術演算を挿入して変換を正しく行う必要があります。

これが、ポインタのみを扱っている場合でも、継承を宣言できない理由です。

なぜ有用なのかについては、キャストを使用する代わりに、共変の戻り値型を使用する場合にコンパイル時間を改善します。たとえば、これはコンパイルされません。

class RA;
class A             { public: virtual RA *fooRet(); };
class RB;
class B : public A  { public: virtual RB *fooRet(); };

しかし、これは:

class RA;
class A             { public: virtual RA *fooRet(); };
class RA { int x; };
class RB : public RA{ int y; };
class B : public A  { public: virtual RB *fooRet(); };

これは、タイプBのオブジェクト(ポインターまたは参照ではない)がある場合に役立ちます。この場合、コンパイラーは直接関数呼び出しを使用できるほどスマートであり、キャストせずにRB *の戻り値の型を直接使用できます。この場合、通常は戻り値の型RA *を作成し、戻り値に対して静的キャストを実行します。

18
user1332054