5

継承について読んでいて、何時間も解決できなかった大きな問題があります。

Barクラスがvirtual関数を持つクラスであるとすると、

class Bar
{
    virtual void Cook();
};

次の違いは何ですか:

class Foo : public Bar
{
    virtual void Cook();
};

class Foo : public virtual Bar
{
    virtual void Cook();
};

? 何時間にもわたってグーグルで調べたり読んだりして、その使用法に関する多くの情報が得られましたが、実際には 2 つの違いが何であるかを教えてくれるものはなく、さらに混乱するだけです。

4

4 に答える 4

5

機能的には、2 つのバージョンに大きな違いはありません。継承の場合、通常、すべての実装で (関数の場合と同様に) virtual(like) ポインターが追加されます。これは、複数の継承 (ダイヤモンド継承の問題)によって生成される複数の基本クラスのコピーを回避するのに役立ちます。vptrvirtual

また、virtual継承は、その基本クラスのコンストラクターを呼び出す権利を委任します。例えば、

class Bar;
class Foo : public virtual Bar
class Other : public Foo  // <--- one more level child class

したがって、今Bar::Bar()から直接呼び出されOther::Other()、他の基本クラスの中で最初に配置されます。

この委任final class機能は、C++03 で (Java で) 機能を実装するのに役立ちます。

class Final {
  Final() {}
  friend class LastClass;
};

class LastClass : virtual Final {  // <--- 'LastClass' is not derivable
...
};

class Child : public LastClass { // <--- not possible to have object of 'Child'
};
于 2011-11-16T08:14:42.303 に答える
4

仮想継承は、クラスが から継承する場合にのみ関連し Fooます。次のように定義すると:

class B {};
class L : virtual public B {};
class R : virtual public B {};
class D : public L, public R {};

その場合、最終的なオブジェクトには、 と の両方で共有される のコピーが 1 つだけ含まれBます 。がなければ、タイプ のオブジェクトには の2 つのコピーが含まれ、1 つはに、もう 1 つは に含まれます。LRvirtualDBLR

すべての継承は仮想でなければならないという議論があります (それが違いを生む場合、ほとんどの場合、それが必要になるためです)。ただし、実際には、仮想継承は高価であり、ほとんどの場合、必要ありません。適切に設計されたシステムでは、ほとんどの継承は、1 つまたは複数の「インターフェイス」から継承する具体的なクラスのものになります。このような具象クラスは通常、それ自体から派生するようには設計されていないため、問題はありません。ただし、重要な例外があります。たとえば、インターフェイスを定義してからインターフェイスの拡張を定義する場合、具体的な実装では複数の拡張を実装する必要があるため、拡張は基本インターフェイスから仮想的に継承する必要があります。または、特定のクラスがインターフェースの一部のみを実装する mixin を設計している場合、最終的なクラスは、これらのクラスのいくつかを継承します (インターフェイスのパーツごとに 1 つ)。最後に、実質的に継承するかどうかの基準はそれほど難しくありません。

  • 継承が公開されていない場合は、おそらく仮想であってはなりません (例外は見たことがありません)。

  • クラスが基本クラスになるように設計されていない場合、仮想継承は必要ありません。

  • 継承は仮想でなければなりません。

いくつかの例外がありますが、上記の規則は安全性の面で誤りです。仮想継承が必要ない場合でも、通常は仮想継承が「正しい」です。

最後のポイント: 仮想ベースは、直接継承する (そして継承が仮想であることを宣言する)クラスではなく、常に最も派生したクラスによって初期化する必要があります。ただし、実際には、これは問題ではありません。仮想継承が理にかなっているケースを見ると、それは常にインターフェイスから継承するケースであり、データが含まれていないため、デフォルトのコンストラクター (のみ) があります。引数を取るコンストラクターを使用してクラスから仮想的に継承していることに気付いた場合は、設計についていくつかの深刻な質問をする時が来ました。

于 2011-11-16T09:35:06.540 に答える
3

この場合、違いはありません。仮想継承は、派生クラスによるスーパークラス サブオブジェクト インスタンスの共有に関連しています。

struct A
{
  int a;
};

struct B : public virtual A
{
  int b;
}

struct C : public virtual A
{
  int c;
};

struct D : public B, public C
{
};

aのインスタンスにはメンバー変数のコピーが 1 つありますDAが仮想基本クラスでない場合A、 のインスタンスには 2 つのサブオブジェクトがありますD

于 2011-11-16T08:16:31.980 に答える
0

仮想関数は、おそらく派生クラスで異なる実装を持つ関数です (必須ではありませんが)。

最後の例は仮想継承です。基本クラス (「ベース」と呼びましょう) から派生した 2 つのクラス (A と B) がある場合を想像してください。ここで、A と B から派生した 3 番目のクラス C を想像してください。仮想継承がなければ、C には「Base」の 2 つのコピーが含まれます。これにより、コンパイル中にあいまいさが生じる可能性があります。仮想継承で重要なことは、A および B からのそのような呼び出しは無視されるため、'Base' クラス コンストラクター (存在する場合) のパラメーターをクラス C で提供する必要があることです。

于 2011-11-16T08:17:43.783 に答える