C ++では、仮想基本クラスとは何ですか?


403

仮想基本クラス」について知りたい」とは何か、その意味。

例を示しましょう:

class Foo
{
public:
    void DoSomething() { /* ... */ }
};

class Bar : public virtual Foo
{
public:
    void DoSpecific() { /* ... */ }
};

クラスAにメンバー変数int aがあり、クラスBにもメンバーint aがあり、クラスcがクラスAとBを継承している場合は、「多重継承」で仮想基本クラスを使用する必要があります。
Namit Sinha 2014

2
@NamitSinhaいいえ、仮想継承はその問題を解決しませ。メンバーaはとにかくあいまいです
Ichthyo

@NamitSinha仮想継承は、継承に関連する複数のあいまいさを取り除く魔法のツールではありません。それは間接的なベースを複数回持つことの「問題」を「解決」します。これは、共有することを意図した場合にのみ問題になります(多くの場合、常にではありません)。
curiousguy

回答:


533

仮想継承で使用される仮想基本クラスは、複数の継承を使用するときに、特定のクラスの複数の「インスタンス」が継承階層に表示されないようにする方法です。

次のシナリオを検討してください。

class A { public: void Foo() {} };
class B : public A {};
class C : public A {};
class D : public B, public C {};

上記のクラス階層は、次のような「恐ろしいひし形」になります。

  A
 / \
B   C
 \ /
  D

Dのインスタンスは、Aを含むBと、Aを含むCで構成されます。したがって、Aの2つの「インスタンス」(より良い表現が必要)があります。

このシナリオがあると、あいまいになる可能性があります。これを行うとどうなりますか:

D d;
d.Foo(); // is this B's Foo() or C's Foo() ??

この問題を解決するために仮想継承があります。クラスを継承するときにvirtualを指定すると、単一のインスタンスのみが必要であることをコンパイラーに伝えます。

class A { public: void Foo() {} };
class B : public virtual A {};
class C : public virtual A {};
class D : public B, public C {};

これは、Aの「インスタンス」が1つだけ階層に含まれていることを意味します。したがって

D d;
d.Foo(); // no longer ambiguous

これはミニサマリーです。詳細については、thisthisを読んでください。良い例もここにあります


7
@Bohdanいいえ、それはしません:)
OJ。

6
@OJ。何故なの?彼らは陽気です:)
Bohdan

15
@Bohdanはvirtualキーワードをできるだけ使用しません。なぜなら、私たちがvirtualキーワードを使用すると、重いメカニズムが適用されるためです。そのため、プログラムの効率が低下します。
Sagar

73
「ひしひしひし形」の図は、一般的に使用されているようですが、混乱を招きます。これは実際に、オブジェクトのレイアウトではなく、クラスの継承関係を示す図です。混乱するのは、を使用するvirtualと、オブジェクトのレイアウトがひし形のように見えることです。使用しない場合virtual、オブジェクトのレイアウトは2つAのs を含むツリー構造のように見えます
MM

5
私はMMによって概説された理由のためにこの回答に反対票を投じなければなりません-図は投稿の反対を表しています。
David Stone、

251

メモリレイアウトについて

補足として、Dreaded Diamondの問題は、基本クラスが複数回存在することです。したがって、通常の継承では、次のように考えています。

  A
 / \
B   C
 \ /
  D

しかし、メモリレイアウトでは、次のようになります。

A   A
|   |
B   C
 \ /
  D

これはD::foo()、を呼び出すときに曖昧さの問題がある理由を説明しています。ただし、実際の問題は、のメンバー変数を使用するときに発生しますA。たとえば、次のようなものだとします。

class A
{
    public :
       foo() ;
       int m_iValue ;
} ;

あなたがアクセスしようとするでしょうときm_iValueからのD階層で、それは2表示されますので、コンパイラは、抗議しますm_iValue、ではないものを。そして、たとえばB::m_iValue(のA::m_iValue親であるB)をC::m_iValue変更しても、変更されません(つまり、のA::m_iValue親です)。C)。

ここで、仮想継承が便利になります。仮想継承は、1つのfoo()メソッドだけでなく1つだけの真のダイヤモンドレイアウトに戻りますm_iValue

何がうまくいかないのでしょうか?

想像してみてください:

  • A いくつかの基本的な機能があります。
  • B それにある種のクールなデータ配列を追加します(例)
  • Cオブザーバーパターン(たとえば、m_iValue)のようないくつかのクールな機能を追加します。
  • DBおよびから継承されるCため、から継承されAます。

通常の継承では、m_iValueからの変更Dはあいまいであり、これを解決する必要があります。たとえあったとしても、中には2つm_iValuesありますDので、それを覚えて、2つを同時に更新することをお勧めします。

仮想継承では、m_iValuefromからの変更は問題Dありません...しかし...あなたが持っているとしましょうD。そのCインターフェースを介して、オブザーバーを接続しました。そして、そのBインターフェイスを介して、クールな配列を更新します。これには、直接変更するという副作用がありm_iValueます...

の変更はm_iValue(仮想アクセサメソッドを使用せずに)直接行わCれるため、リスニングを実装するコードがにありCBそれを認識していないため、「リスニング」スルーを介したオブザーバは呼び出されません...

結論

階層にひし形がある場合は、その階層で何か問題があった可能性が95%あることを意味します。


「問題が発生する可能性があります」は、多重継承によるのではなく、基本メンバーへの直接アクセスが原因です。「B」を取り除くと、同じ問題が発生します。基本的なルール:「プライベートでない場合は仮想である必要がある」が問題を回避します。m_iValueは仮想ではないため、プライベートである必要があります
Chris Dodd

4
@Chris Dodd:そうではありません。m_iValueで何が起こるかは、すべてのシンボル(typedef、メンバー変数、メンバー関数、基本クラスへのキャストなど)で起こりました。これは実際には多重継承の問題であり、Javaの方法ではなく、多重継承を正しく使用することをユーザーが認識し、「多重継承は100%悪である、インターフェースでそれをやろう」と結論づけるべき問題です。
paercebal

こんにちは、仮想キーワードを使用すると、Aのコピーは1つだけになります。私の質問は、それがBからのものか、Cからのものかをどのようにして知るのですか?私の質問はまったく有効ですか?
user875036 14年

@ user875036:AはBとCの両方から送信されます。実際、仮想性はいくつかの変更を行います(たとえば、DはBでもCでもなく、Aのコンストラクターを呼び出します)。B及びC(及びD)の両方は、Aへのポインタを持っている
paercebal

3
FWIW、誰かが不思議に思っている場合、メンバー変数仮想にすることはできません-仮想は関数の指定子です。SO参照:stackoverflow.com/questions/3698831/...は
rholmes

34

仮想ベースを使用した多重継承について説明するには、C ++オブジェクトモデルの知識が必要です。また、トピックを明確に説明することは、コメントボックスではなく記事で行うのが最善です。

このテーマに関する私の疑問をすべて解決した、私が見つけた最良の読みやすい説明は、次の記事でした。 。http //www.phpcompiler.org/articles/virtualinheritance.html

それを読んだ後は、(コンパイラの作成者でない限り)トピックについて他に何も読む必要はありません...


10

仮想基本クラスはインスタンス化できないクラスです。そのクラスから直接オブジェクトを作成することはできません。

私はあなたが2つの非常に異なるものを混同していると思います。仮想継承は、抽象クラスと同じものではありません。仮想継承は、関数呼び出しの動作を変更します。場合によってはあいまいな関数呼び出しを解決することもあれば、非仮想継承で期待されるもの以外のクラスへの関数呼び出し処理を延期することもあります。


7

OJの親切な説明に追加したいと思います。

仮想継承には、代償が伴います。すべての仮想物と同様に、パフォーマンスに打撃を与えます。このパフォーマンスへの影響を回避する方法は、あまりエレガントではない可能性があります。

仮想的に派生してダイヤモンドを壊す代わりに、ダイヤモンドに別のレイヤーを追加して、次のようなものを得ることができます:

   B
  / \
D11 D12
 |   |
D21 D22
 \   /
  DD

どのクラスも仮想的に継承せず、すべてパブリックに継承します。クラスD21とD22は、おそらく関数をプライベートとして宣言することによって、DDに対してあいまいな仮想関数f()を隠します。それらはそれぞれラッパー関数f1()とf2()を定義し、それぞれがクラスローカル(プライベート)f()を呼び出して、競合を解決します。クラスDDは、D11 :: f()が必要な場合はf1()を呼び出し、D12 :: f()が必要な場合はf2()を呼び出します。インラインでラッパーを定義すると、オーバーヘッドはほぼゼロになります。

もちろん、D11とD12を変更できる場合は、これらのクラス内で同じトリックを実行できますが、多くの場合そうではありません。


2
これは多かれ少なかれエレガントなことやあいまいさを解決することの問題ではありません(そのために常に明示的なxxx ::仕様を使用できます)。非仮想継承では、クラスDDのすべてのインスタンスにBの2つの独立したインスタンスがあります。クラスに単一の非静的データメンバーがあるとすぐに、仮想継承と非仮想継承は単なる構文以上に異なります。
user3489112 2014年

@ user3489112すぐに...何も。仮想継承と非仮想継承は、意味的に期間が異なります。
curiousguy


1

あなたは少し混乱しています。いくつかの概念を混同しているかどうかはわかりません。

OPに仮想基本クラスがありません。あなただけの基本クラスがあります。

あなたは仮想継承を行いました。これは通常、複数の継承で使用されるため、複数の派生クラスは基本クラスのメンバーを複製せずに使用します。

純粋な仮想関数を持つ基本クラスはインスタンス化されません。これには、Paulが理解する構文が必要です。これは通常、派生クラスがこれらの関数を定義する必要があるために使用されます。

私はこれについてこれ以上説明したくありません。あなたが求めていることを完全には理解していないからです。


1
仮想継承で使用される「基本クラス」は、(その正確な継承のコンテキストでは)「仮想基本クラス」になります。
Luc Hermitte

1

これは、仮想関数の呼び出しが「正しい」クラスに転送されることを意味します。

C ++ FAQ Lite FTW。

つまり、「ダイアモンド」階層が形成される複数継承シナリオでよく使用されます。次に、仮想継承は、そのクラスで関数を呼び出し、関数がその最下位クラスの上のクラスD1またはD2のいずれかに解決される必要がある場合、最下位クラスで作成されたあいまいさを解消します。FAQ項目を参照してください図と詳細をください。

強力な機能である姉妹委任でも使用されます(ただし、気弱な人には使用できません)。これを FAQをください。

また、Effective C ++第3版の項目40(第2版の43)も参照してください。


1

ダイヤモンド継承の実行可能な使用例

この例は、典型的なシナリオで仮想基本クラスを使用する方法を示しています:ダイヤモンドの継承を解決します。

#include <cassert>

class A {
    public:
        A(){}
        A(int i) : i(i) {}
        int i;
        virtual int f() = 0;
        virtual int g() = 0;
        virtual int h() = 0;
};

class B : public virtual A {
    public:
        B(int j) : j(j) {}
        int j;
        virtual int f() { return this->i + this->j; }
};

class C : public virtual A {
    public:
        C(int k) : k(k) {}
        int k;
        virtual int g() { return this->i + this->k; }
};

class D : public B, public C {
    public:
        D(int i, int j, int k) : A(i), B(j), C(k) {}
        virtual int h() { return this->i + this->j + this->k; }
};

int main() {
    D d = D(1, 2, 4);
    assert(d.f() == 3);
    assert(d.g() == 5);
    assert(d.h() == 7);
}

2
assert(A::aDefault == 0);メイン関数からのコンパイルエラー:aDefault is not a member of Agcc 5.4.0の使用。何をするつもりですか?
SebNag 2017年

@SebTuああありがとう。コピーペーストから削除するのを忘れただけで、すぐに削除しました。この例は、それがなくても意味があるはずです。
Ciro Santilli郝海东冠状病六四事件法轮功

0

仮想クラスがありません仮想継承と同じで。インスタンス化できない仮想クラス、仮想継承はまったく別のものです。

ウィキペディアは、私よりもよく説明しています。http://en.wikipedia.org/wiki/Virtual_inheritance


6
C ++には「仮想クラス」などはありません。ただし、特定の継承に関して「仮想」である「仮想基本クラス」があります。あなたが参照するのは、正式に「抽象クラス」と呼ばれるものです。
Luc Hermitte

@ LucHermitte、C ++には間違いなく仮想クラスがあります。これを確認してください:en.wikipedia.org/wiki/Virtual_class
Rafid 2014年

「エラー:「仮想」は関数に対してのみ指定できます」。これが何語かわかりません。しかし、C ++には仮想クラスのようなものは明確にありません。
Luc

0

通常の継承

典型的な3レベルの非ダイヤモンド、非仮想継承の継承では、新しい最も派生したオブジェクトをインスタンス化すると、newが呼び出され、オブジェクトに必要なサイズがコンパイラーによってクラス型から解決され、newに渡されます。

newには署名があります:

_GLIBCXX_WEAK_DEFINITION void *
operator new (std::size_t sz) _GLIBCXX_THROW (std::bad_alloc)

そして、 malloc、voidポインタを返します

次に、これは最も派生したオブジェクトのコンストラクターに渡されます。コンストラクターはすぐに中央のコンストラクターを呼び出し、次に中央のコンストラクターがすぐに基本コンストラクターを呼び出します。次に、ベースは、オブジェクトの開始時に仮想テーブルへのポインタを格納し、その後にその属性を格納します。次に、これは中央のコンストラクターに戻ります。中央のコンストラクターは、仮想テーブルポインターを同じ場所に格納し、その後、基本コンストラクターによって格納されたであろう属性の後にその属性を格納します。これは、最も派生したコンストラクターに戻ります。このコンストラクターは、仮想テーブルへのポインターを同じ場所に格納し、その後、中間のコンストラクターによって格納されたであろう属性の後にその属性を格納します。

仮想テーブルポインタが上書きされるため、仮想テーブルポインタは常に最も派生したクラスの1つになります。仮想性は最も派生したクラスに向かって伝播するため、関数が中間クラスで仮想である場合、関数は最も派生したクラスでは仮想になりますが、基本クラスでは仮想になります。最も派生したクラスのインスタンスを基本クラスへのポインターに多態的にキャストすると、コンパイラーはこれを仮想テーブルへの間接呼び出しに解決せず、代わりに関数を直接呼び出しますA::function()。関数をキャストした型に対して仮想である場合、関数は、常に最も派生したクラスの仮想テーブルへの呼び出しに解決されます。そのタイプの仮想ではない場合、それは単に呼び出されますType::function()、オブジェクトポインターをて渡し、Typeにキャストします。

実際、私がその仮想テーブルへのポインターを言うとき、それは実際には常に仮想テーブルへの16のオフセットです。

vtable for Base:
        .quad   0
        .quad   typeinfo for Base
        .quad   Base::CommonFunction()
        .quad   Base::VirtualFunction()

pointer is typically to the first function i.e. 

        mov     edx, OFFSET FLAT:vtable for Base+16

virtualそれが伝播するために、それがより少ない派生クラスで仮想である場合、より多くの派生クラスで再び必要とされません。ただし、この関数を使用して、関数が実際に仮想関数であることを示すことができます。継承するクラスの型定義を確認する必要はありません。

override この関数は何かをオーバーライドしていて、そうでない場合はコンパイラエラーをスローすると言う別のコンパイラガードです。

= 0 これは抽象的な関数であることを意味します

final より派生したクラスで仮想関数が再度実装されるのを防ぎ、最も派生したクラスの仮想テーブルにそのクラスの最終関数が含まれるようにします。

= default ドキュメントでコンパイラがデフォルトの実装を使用することを明示します

= delete これを呼び出そうとするとコンパイラエラーが発生する

仮想継承

検討する

class Base
  {
      int a = 1;
      int b = 2;
  public:
      void virtual CommonFunction(){} ;
      void virtual VirtualFunction(){} ;
  };


class DerivedClass1: virtual public Base
  {
      int c = 3;
  public:
    void virtual DerivedCommonFunction(){} ;
     void virtual VirtualFunction(){} ;
  };

  class DerivedClass2 : virtual public Base
 {
     int d = 4;
 public:
     //void virtual DerivedCommonFunction(){} ;    
     void virtual VirtualFunction(){} ;
     void virtual DerivedCommonFunction2(){} ;
 };

class DerivedDerivedClass :  public DerivedClass1, public DerivedClass2
 {
   int e = 5;
 public:
     void virtual DerivedDerivedCommonFunction(){} ;
     void virtual VirtualFunction(){} ;
 };

 int main () {
   DerivedDerivedClass* d = new DerivedDerivedClass;
   d->VirtualFunction();
   d->DerivedCommonFunction();
   d->DerivedCommonFunction2();
   d->DerivedDerivedCommonFunction();
   ((DerivedClass2*)d)->DerivedCommonFunction2();
   ((Base*)d)->VirtualFunction();
 }

低音クラスを仮想的に継承しないと、次のようなオブジェクトが得られます。

これの代わりに:

つまり、2つの基本オブジェクトがあります。

仮想ダイヤモンド継承の状況で上記、新しいが呼び出された後、それはほとんどの派生コンストラクタを呼び出し、そのコンストラクタで、それはその仮想テーブルのテーブルにオフセットを通過するすべての3つの派生コンストラクタを呼び出して、だけではなく、呼び出しを呼び出すのDerivedClass1::DerivedClass1()DerivedClass2::DerivedClass2()、それらの両方の呼び出しをして、Base::Base()

以下はすべてデバッグモード-O0でコンパイルされているため、冗長アセンブリがあります。

main:
.LFB8:
        push    rbp
        mov     rbp, rsp
        push    rbx
        sub     rsp, 24
        mov     edi, 48 //pass size to new
        call    operator new(unsigned long) //call new
        mov     rbx, rax  //move the address of the allocation to rbx
        mov     rdi, rbx  //move it to rdi i.e. pass to the call
        call    DerivedDerivedClass::DerivedDerivedClass() [complete object constructor] //construct on this address
        mov     QWORD PTR [rbp-24], rbx  //store the address of the object on the stack as d
DerivedDerivedClass::DerivedDerivedClass() [complete object constructor]:
.LFB20:
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        mov     QWORD PTR [rbp-8], rdi
.LBB5:
        mov     rax, QWORD PTR [rbp-8] // object address now in rax 
        add     rax, 32 //increment address by 32
        mov     rdi, rax // move object address+32 to rdi i.e. pass to call
        call    Base::Base() [base object constructor]
        mov     rax, QWORD PTR [rbp-8] //move object address to rax
        mov     edx, OFFSET FLAT:VTT for DerivedDerivedClass+8 //move address of VTT+8 to edx
        mov     rsi, rdx //pass VTT+8 address as 2nd parameter 
        mov     rdi, rax //object address as first
        call    DerivedClass1::DerivedClass1() [base object constructor]
        mov     rax, QWORD PTR [rbp-8] //move object address to rax
        add     rax, 16  //increment object address by 16
        mov     edx, OFFSET FLAT:VTT for DerivedDerivedClass+24  //store address of VTT+24 in edx
        mov     rsi, rdx //pass address of VTT+24 as second parameter
        mov     rdi, rax //address of object as first
        call    DerivedClass2::DerivedClass2() [base object constructor]
        mov     edx, OFFSET FLAT:vtable for DerivedDerivedClass+24 //move this to edx
        mov     rax, QWORD PTR [rbp-8] // object address now in rax
        mov     QWORD PTR [rax], rdx. //store address of vtable for DerivedDerivedClass+24 at the start of the object
        mov     rax, QWORD PTR [rbp-8] // object address now in rax
        add     rax, 32  // increment object address by 32
        mov     edx, OFFSET FLAT:vtable for DerivedDerivedClass+120 //move this to edx
        mov     QWORD PTR [rax], rdx  //store vtable for DerivedDerivedClass+120 at object+32 (Base) 
        mov     edx, OFFSET FLAT:vtable for DerivedDerivedClass+72 //store this in edx
        mov     rax, QWORD PTR [rbp-8] //move object address to rax
        mov     QWORD PTR [rax+16], rdx //store vtable for DerivedDerivedClass+72 at object+16 (DerivedClass2)
        mov     rax, QWORD PTR [rbp-8]
        mov     DWORD PTR [rax+28], 5
.LBE5:
        nop
        leave
        ret

Base::Base()オブジェクトオフセット32へのポインターを使用して呼び出します。Baseは、受け取ったアドレスとそのメンバーにある仮想テーブルへのポインターを格納します。

Base::Base() [base object constructor]:
.LFB11:
        push    rbp
        mov     rbp, rsp
        mov     QWORD PTR [rbp-8], rdi //stores address of object on stack (-O0)
.LBB2:
        mov     edx, OFFSET FLAT:vtable for Base+16  //puts vtable for Base+16 in edx
        mov     rax, QWORD PTR [rbp-8] //copies address of object from stack to rax
        mov     QWORD PTR [rax], rdx  //stores it address of object
        mov     rax, QWORD PTR [rbp-8] //copies address of object on stack to rax again
        mov     DWORD PTR [rax+8], 1 //stores a = 1 in the object
        mov     rax, QWORD PTR [rbp-8] //junk from -O0
        mov     DWORD PTR [rax+12], 2  //stores b = 2 in the object
.LBE2:
        nop
        pop     rbp
        ret

DerivedDerivedClass::DerivedDerivedClass()次にDerivedClass1::DerivedClass1()、オブジェクトオフセット0へのポインタで呼び出し、アドレスを渡しますVTT for DerivedDerivedClass+8

DerivedClass1::DerivedClass1() [base object constructor]:
.LFB14:
        push    rbp
        mov     rbp, rsp
        mov     QWORD PTR [rbp-8], rdi //address of object
        mov     QWORD PTR [rbp-16], rsi  //address of VTT+8
.LBB3:
        mov     rax, QWORD PTR [rbp-16]  //address of VTT+8 now in rax
        mov     rdx, QWORD PTR [rax]     //address of DerivedClass1-in-DerivedDerivedClass+24 now in rdx
        mov     rax, QWORD PTR [rbp-8]   //address of object now in rax
        mov     QWORD PTR [rax], rdx     //store address of DerivedClass1-in-.. in the object
        mov     rax, QWORD PTR [rbp-8]  // address of object now in rax
        mov     rax, QWORD PTR [rax]    //address of DerivedClass1-in.. now implicitly in rax
        sub     rax, 24                 //address of DerivedClass1-in-DerivedDerivedClass+0 now in rax
        mov     rax, QWORD PTR [rax]    //value of 32 now in rax
        mov     rdx, rax                // now in rdx
        mov     rax, QWORD PTR [rbp-8]  //address of object now in rax
        add     rdx, rax                //address of object+32 now in rdx
        mov     rax, QWORD PTR [rbp-16]  //address of VTT+8 now in rax
        mov     rax, QWORD PTR [rax+8]   //address of DerivedClass1-in-DerivedDerivedClass+72 (Base::CommonFunction()) now in rax
        mov     QWORD PTR [rdx], rax     //store at address object+32 (offset to Base)
        mov     rax, QWORD PTR [rbp-8]  //store address of object in rax, return
        mov     DWORD PTR [rax+8], 3    //store its attribute c = 3 in the object
.LBE3:
        nop
        pop     rbp
        ret
VTT for DerivedDerivedClass:
        .quad   vtable for DerivedDerivedClass+24
        .quad   construction vtable for DerivedClass1-in-DerivedDerivedClass+24
        .quad   construction vtable for DerivedClass1-in-DerivedDerivedClass+72
        .quad   construction vtable for DerivedClass2-in-DerivedDerivedClass+24
        .quad   construction vtable for DerivedClass2-in-DerivedDerivedClass+72
        .quad   vtable for DerivedDerivedClass+120
        .quad   vtable for DerivedDerivedClass+72

construction vtable for DerivedClass1-in-DerivedDerivedClass:
        .quad   32
        .quad   0
        .quad   typeinfo for DerivedClass1
        .quad   DerivedClass1::DerivedCommonFunction()
        .quad   DerivedClass1::VirtualFunction()
        .quad   -32
        .quad   0
        .quad   -32
        .quad   typeinfo for DerivedClass1
        .quad   Base::CommonFunction()
        .quad   virtual thunk to DerivedClass1::VirtualFunction()
construction vtable for DerivedClass2-in-DerivedDerivedClass:
        .quad   16
        .quad   0
        .quad   typeinfo for DerivedClass2
        .quad   DerivedClass2::VirtualFunction()
        .quad   DerivedClass2::DerivedCommonFunction2()
        .quad   -16
        .quad   0
        .quad   -16
        .quad   typeinfo for DerivedClass2
        .quad   Base::CommonFunction()
        .quad   virtual thunk to DerivedClass2::VirtualFunction()
vtable for DerivedDerivedClass:
        .quad   32
        .quad   0
        .quad   typeinfo for DerivedDerivedClass
        .quad   DerivedClass1::DerivedCommonFunction()
        .quad   DerivedDerivedClass::VirtualFunction()
        .quad   DerivedDerivedClass::DerivedDerivedCommonFunction()
        .quad   16
        .quad   -16
        .quad   typeinfo for DerivedDerivedClass
        .quad   non-virtual thunk to DerivedDerivedClass::VirtualFunction()
        .quad   DerivedClass2::DerivedCommonFunction2()
        .quad   -32
        .quad   0
        .quad   -32
        .quad   typeinfo for DerivedDerivedClass
        .quad   Base::CommonFunction()
        .quad   virtual thunk to DerivedDerivedClass::VirtualFunction()

virtual thunk to DerivedClass1::VirtualFunction():
        mov     r10, QWORD PTR [rdi]
        add     rdi, QWORD PTR [r10-32]
        jmp     .LTHUNK0
virtual thunk to DerivedClass2::VirtualFunction():
        mov     r10, QWORD PTR [rdi]
        add     rdi, QWORD PTR [r10-32]
        jmp     .LTHUNK1
virtual thunk to DerivedDerivedClass::VirtualFunction():
        mov     r10, QWORD PTR [rdi]
        add     rdi, QWORD PTR [r10-32]
        jmp     .LTHUNK2
non-virtual thunk to DerivedDerivedClass::VirtualFunction():
        sub     rdi, 16
        jmp     .LTHUNK3

        .set    .LTHUNK0,DerivedClass1::VirtualFunction()
        .set    .LTHUNK1,DerivedClass2::VirtualFunction()
        .set    .LTHUNK2,DerivedDerivedClass::VirtualFunction()
        .set    .LTHUNK3,DerivedDerivedClass::VirtualFunction()

DerivedDerivedClass::DerivedDerivedClass()その後、オブジェクト+ 16のアドレスとのためのVTTのアドレスを通過DerivedDerivedClass+24DerivedClass2::DerivedClass2()、そのアセンブリと同一であるDerivedClass1::DerivedClass1()行を除いてmov DWORD PTR [rax+8], 3、明らか4の代わりに3を有していますd = 4

この後、オブジェクト内の3つの仮想テーブルポインターすべてDerivedDerivedClassを、そのクラスの表現へのvtable 内のオフセットへのポインターに置き換えます。

d->VirtualFunction();

        mov     rax, QWORD PTR [rbp-24] //store pointer to virtual table in rax 
        mov     rax, QWORD PTR [rax] //dereference and store in rax
        add     rax, 8 // call the 2nd function in the table
        mov     rdx, QWORD PTR [rax] //dereference 
        mov     rax, QWORD PTR [rbp-24]
        mov     rdi, rax
        call    rdx

d->DerivedCommonFunction();

        mov     rax, QWORD PTR [rbp-24]
        mov     rdx, QWORD PTR [rbp-24]
        mov     rdx, QWORD PTR [rdx]
        mov     rdx, QWORD PTR [rdx]
        mov     rdi, rax
        call    rdx

d->DerivedCommonFunction2();

        mov     rax, QWORD PTR [rbp-24]
        lea     rdx, [rax+16]
        mov     rax, QWORD PTR [rbp-24]
        mov     rax, QWORD PTR [rax+16]
        add     rax, 8
        mov     rax, QWORD PTR [rax]
        mov     rdi, rdx
        call    rax

d->DerivedDerivedCommonFunction();

        mov     rax, QWORD PTR [rbp-24]
        mov     rax, QWORD PTR [rax]
        add     rax, 16
        mov     rdx, QWORD PTR [rax]
        mov     rax, QWORD PTR [rbp-24]
        mov     rdi, rax
        call    rdx

((DerivedClass2*)d)->DerivedCommonFunction2();

        cmp     QWORD PTR [rbp-24], 0
        je      .L14
        mov     rax, QWORD PTR [rbp-24]
        add     rax, 16
        jmp     .L15
.L14:
        mov     eax, 0
.L15:
        cmp     QWORD PTR [rbp-24], 0
        cmp     QWORD PTR [rbp-24], 0
        je      .L18
        mov     rdx, QWORD PTR [rbp-24]
        add     rdx, 16
        jmp     .L19
.L18:
        mov     edx, 0
.L19:
        mov     rdx, QWORD PTR [rdx]
        add     rdx, 8
        mov     rdx, QWORD PTR [rdx]
        mov     rdi, rax
        call    rdx

((Base*)d)->VirtualFunction();

        cmp     QWORD PTR [rbp-24], 0
        je      .L20
        mov     rax, QWORD PTR [rbp-24]
        mov     rax, QWORD PTR [rax]
        sub     rax, 24
        mov     rax, QWORD PTR [rax]
        mov     rdx, rax
        mov     rax, QWORD PTR [rbp-24]
        add     rax, rdx
        jmp     .L21
.L20:
        mov     eax, 0
.L21:
        cmp     QWORD PTR [rbp-24], 0
        cmp     QWORD PTR [rbp-24], 0
        je      .L24
        mov     rdx, QWORD PTR [rbp-24]
        mov     rdx, QWORD PTR [rdx]
        sub     rdx, 24
        mov     rdx, QWORD PTR [rdx]
        mov     rcx, rdx
        mov     rdx, QWORD PTR [rbp-24]
        add     rdx, rcx
        jmp     .L25
.L24:
        mov     edx, 0
.L25:
        mov     rdx, QWORD PTR [rdx]
        add     rdx, 8
        mov     rdx, QWORD PTR [rdx]
        mov     rdi, rax
        call    rdx
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.