4

ダイアモンド型の継承はあいまいさを引き起こし、継承を使用することで回避できることを理解してvirtual Base Classesいます。問題はそれに関するものではありません。問題は、クラスがポリモーフィックである場合の、ひし形の階層で最も派生したクラスのサイズについてです。サンプルコードとサンプル出力は次のとおりです。

#include<iostream>

using namespace std;

class Base
{
    public:
        virtual void doSomething(){}  
};

class Derived1:public virtual Base
{
    public:
       virtual void doSomething(){}
};

class Derived2:public virtual Base
{
    public:
       virtual void doSomething(){}
};

class Derived3:public Derived1,public Derived2
{
    public:
       virtual void doSomething(){}
};

int main()
{
    Base obj;
    Derived1 objDerived1;
    Derived2 objDerived2;
    Derived3 objDerived3;

    cout<<"\n Size of Base: "<<sizeof(obj);
    cout<<"\n Size of Derived1: "<<sizeof(objDerived1);
    cout<<"\n Size of Derived2: "<<sizeof(objDerived2);
    cout<<"\n Size of Derived3: "<<sizeof(objDerived3);

    return 0;
}

私が得る出力は次のとおりです。

 Size of Base: 4
 Size of Derived1: 4
 Size of Derived2: 4
 Size of Derived3: 8

私が理解しているようBaseに、仮想メンバー関数が含まれているため、
この環境では、sizeof Base=vptrのサイズ=4です。

Derived1ケースとDerived2クラスも同様です。

上記のシナリオに関連する私の質問は次のとおりです。クラスオブジェクト
のサイズはどうDerived3ですか。Derived3クラスに2つのvptrがあるということですか?
クラスはこれらの2つのvptrでどのDerived3ように機能しますか、それが使用するメカニズムについてのアイデアはありますか?
クラスのsizeofは、コンパイラの実装の詳細として残され、標準では定義されていません(仮想メカニズム自体はコンパイラの実装の詳細であるため)?

4

4 に答える 4

4

はい、Derived32つのvtableポインタがあります。値でアクセスしている場合は、Derived3バージョンを使用するか、親から関数を選択するか、決定できない場合はあいまいであることを示します。

子の場合、多態的に使用されている親1/2に対応するvtableを使用します。

仮想継承を正しく使用しなかったことに注意してください。Derived1と2は仮想継承から仮想継承する必要があると思いますBasesizeof(Derived3)として扱うことができる2つの可能な親がまだあるため、まだ8のようDerived3です。親の1つにキャストすると、コンパイラは実際にオブジェクトポインタを調整して正しいvtableを取得します。

また、標準にはvtableについての言及さえないため、vtableに関連するものはすべて実装固有であることを指摘しておく必要があります。

于 2011-03-31T16:23:07.463 に答える
3

コードの小さな修正:仮想は、動作するために、derived2とderived3の定義に含まれているはずです。

http://www.parashift.com/c++-faq-lite/multiple-inheritance.html#faq-25.9

于 2011-03-31T16:24:27.210 に答える
1

完全に実装固有の何かについて疑問に思っていると思います。クラスのサイズについては何も想定しないでください。

編集:好奇心は証明された品質ですが;-)

于 2011-03-31T16:23:54.997 に答える
1

少し異なるケースを考えてみましょう。

struct B { virtual void f(); };
struct L : virtual B { virtual void g(); };
struct R : virtual B { virtual void h(); };
struct D : L, R {};

典型的な実装では、L :: gはLのvtableのR:hと同じ位置(たとえばインデックス0)にあります。次に、次のコードが与えられたときに何が起こるかを考えてみましょう。

D* pd = new D;
L* pl = pd;
R* pr = pd;
pl->g();
pr->h();

最後の2行で、コンパイラはvtableの同じ位置にある関数のアドレスを見つけるためのコードを生成します。したがって、plを介してアクセスされるvtableは、prを介してアクセスされるvtable(またはそのプレフィックス)と同じにすることはできません。したがって、2つの異なるvtableを指すには、オブジェクト全体に少なくとも2つのvptrが必要です。

于 2011-03-31T17:09:32.717 に答える