Di C ++, apa itu kelas dasar virtual?


403

Saya ingin tahu apa itu " kelas dasar virtual " dan apa artinya.

Izinkan saya menunjukkan sebuah contoh:

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

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

haruskah kita menggunakan kelas basis virtual dalam 'pewarisan berganda' karena jika kelas A memiliki variabel anggota int a dan kelas B juga memiliki anggota int a dan kelas c mewarisi kelas A dan B bagaimana kita memutuskan 'a' mana yang akan digunakan?
Namit Sinha

2
@NamitSinha tidak, warisan virtual tidak menyelesaikan masalah itu. Anggota a akan ambigu pula
Ichthyo

@NamitSinha Warisan virtual bukanlah alat ajaib untuk menghapus beberapa ambiguitas terkait pewarisan. Ini "memecahkan" "masalah" memiliki basis tidak langsung lebih dari sekali. Yang hanya masalah jika itu dimaksudkan untuk dibagikan (sering tetapi tidak selalu terjadi).
curiousguy

Jawaban:


533

Kelas dasar virtual, yang digunakan dalam warisan virtual, adalah cara untuk mencegah beberapa "instance" dari kelas yang diberikan muncul dalam hierarki warisan ketika menggunakan banyak pewarisan.

Pertimbangkan skenario berikut:

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

Hirarki kelas di atas menghasilkan "berlian menakutkan" yang terlihat seperti ini:

  A
 / \
B   C
 \ /
  D

Sebuah instance dari D akan terdiri dari B, yang termasuk A, dan C yang juga termasuk A. Jadi Anda memiliki dua "instance" (karena ingin ekspresi yang lebih baik) dari A.

Ketika Anda memiliki skenario ini, Anda memiliki kemungkinan ambiguitas. Apa yang terjadi ketika Anda melakukan ini:

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

Warisan virtual ada untuk menyelesaikan masalah ini. Saat Anda menentukan virtual saat mewarisi kelas Anda, Anda memberi tahu kompiler bahwa Anda hanya menginginkan satu instance.

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

Ini berarti bahwa hanya ada satu "instance" dari A yang termasuk dalam hierarki. Karenanya

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

Ini adalah ringkasan mini. Untuk informasi lebih lanjut, baca ini dan ini . Contoh yang baik juga tersedia di sini .


7
@Bohdan tidak itu tidak :)
OJ.

6
@OJ. kenapa tidak? Mereka lucu :)
Bohdan

15
@Bohdan menggunakan kata kunci virtual sebanyak kurang, karena ketika kita menggunakan kata kunci virtual, mekanisme berat diterapkan. Jadi, efisiensi program Anda akan berkurang.
Sagar

73
Diagram "berlian menakutkan" Anda membingungkan, meskipun tampaknya biasa digunakan. Ini sebenarnya adalah diagram yang menunjukkan hubungan warisan kelas - bukan tata letak objek. Bagian yang membingungkan adalah bahwa jika kita menggunakan virtual, maka tata letak objek terlihat seperti berlian; dan jika kita tidak menggunakan virtualmaka tata letak objek terlihat seperti struktur pohon yang berisi dua As
MM

5
Saya harus downvote jawaban ini karena alasan yang diuraikan oleh MM - diagram menyatakan kebalikan dari posting.
David Stone

251

Tentang tata letak memori

Sebagai catatan, masalah dengan Dreaded Diamond adalah bahwa kelas dasar hadir beberapa kali. Jadi dengan warisan reguler, Anda yakin telah:

  A
 / \
B   C
 \ /
  D

Namun dalam tata letak memori, Anda memiliki:

A   A
|   |
B   C
 \ /
  D

Ini menjelaskan mengapa saat menelepon D::foo(), Anda memiliki masalah ambiguitas. Tetapi masalah sebenarnya muncul ketika Anda ingin menggunakan variabel anggota A. Sebagai contoh, katakanlah kita memiliki:

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

Ketika Anda akan mencoba mengakses m_iValuedari D, kompiler akan memprotes, karena dalam hierarki, ia akan melihat dua m_iValue, bukan satu. Dan jika Anda memodifikasi satu, katakanlah, B::m_iValue(yang merupakan A::m_iValueinduk dari B), C::m_iValuetidak akan dimodifikasi (itu adalah A::m_iValueinduk dariC ).

Di sinilah warisan virtual berguna, karena dengan itu, Anda akan kembali ke tata letak berlian yang sebenarnya, dengan tidak hanya satu foo()metode saja, tetapi juga satu dan hanya satu m_iValue.

Apa yang bisa salah?

Membayangkan:

  • A memiliki beberapa fitur dasar.
  • B menambah semacam array data yang keren (misalnya)
  • Cmenambah beberapa fitur keren seperti pola pengamat (misalnya, aktif m_iValue).
  • Dmewarisi dari Bdan C, dan dengan demikian dari A.

Dengan warisan normal, pengubahan m_iValuedari Dadalah ambigu dan ini harus diselesaikan. Bahkan jika ada, ada dua m_iValuesdi dalamD , jadi Anda sebaiknya mengingatnya dan memperbarui keduanya sekaligus.

Dengan warisan virtual, memodifikasi m_iValuedari Dadalah ok ... Tapi ... Katakanlah Anda miliki D. Melalui Cantarmuka, Anda memasang pengamat. Dan melalui Bantarmuka, Anda memperbarui array keren, yang memiliki efek samping mengubah langsung m_iValue...

Karena perubahan m_iValuedilakukan secara langsung (tanpa menggunakan metode pengakses virtual), pengamat "mendengarkan" melalui Ctidak akan dipanggil, karena kode yang menerapkan mendengarkan dalam C, dan Btidak tahu tentang itu ...

Kesimpulan

Jika Anda memiliki berlian dalam hierarki Anda, itu berarti Anda memiliki kemungkinan 95% untuk melakukan sesuatu yang salah dengan hierarki tersebut.


'Apa yang bisa salah' adalah karena akses langsung ke anggota pangkalan, bukan karena pewarisan berganda. Singkirkan 'B "dan Anda memiliki masalah yang sama. Aturan dasar:' jika ini tidak pribadi, itu harus virtual 'menghindari masalah. M_iValue bukan virtual dan karenanya harus pribadi
Chris Dodd

4
@ Chris Dodd: Tidak juga. Apa yang terjadi dengan m_iValue akan terjadi pada simbol apa pun ( misalnya typedef, variabel anggota, fungsi anggota, dilemparkan ke kelas dasar, dll .). Ini benar-benar adalah masalah pewarisan berganda, masalah yang harus disadari pengguna untuk menggunakan pewarisan berganda dengan benar, alih-alih menggunakan cara Java dan menyimpulkan "Warisan berganda adalah kejahatan 100%, mari kita lakukan dengan antarmuka".
paercebal

Hai, Ketika kita menggunakan kata kunci virtual, hanya akan ada satu salinan A. Pertanyaan saya adalah bagaimana kita tahu apakah itu berasal dari B atau C? Apakah pertanyaan saya benar?
user875036

@ user875036: A berasal dari B dan C. Memang, virtualitas mengubah beberapa hal (misalnya D akan memanggil konstruktor A, bukan B, atau C). Baik B dan C (dan D) memiliki pointer ke A.
paercebal

3
FWIW, jika seseorang bertanya-tanya, variabel anggota tidak bisa virtual - virtual adalah specifier untuk fungsi . Referensi SO: stackoverflow.com/questions/3698831/…
rholmes

34

Menjelaskan multiple-inheritance dengan basis virtual membutuhkan pengetahuan tentang model objek C ++. Dan menjelaskan topiknya dengan jelas paling baik dilakukan dalam sebuah artikel dan bukan dalam kotak komentar.

Penjelasan terbaik dan dapat dibaca yang saya temukan yang menyelesaikan semua keraguan saya pada subjek ini adalah artikel ini: http://www.phpcompiler.org/articles/virtualinheritance.html

Anda benar-benar tidak perlu membaca apa pun tentang topik (kecuali jika Anda adalah penulis kompiler) setelah membaca itu ...


10

Kelas dasar virtual adalah kelas yang tidak bisa dipakai: Anda tidak bisa membuat objek langsung dari itu.

Saya pikir Anda membingungkan dua hal yang sangat berbeda. Warisan virtual tidak sama dengan kelas abstrak. Warisan virtual mengubah perilaku panggilan fungsi; kadang-kadang ia menyelesaikan panggilan fungsi yang sebaliknya akan ambigu, kadang-kadang ia menolak penanganan panggilan fungsi ke kelas selain yang diharapkan dalam warisan non-virtual.


7

Saya ingin menambahkan klarifikasi jenis OJ.

Warisan virtual tidak datang tanpa harga. Seperti halnya semua hal virtual, Anda mendapatkan kinerja yang baik. Ada cara mengatasi hit kinerja ini yang mungkin kurang elegan.

Alih-alih memecahkan berlian dengan menurunkan secara virtual, Anda dapat menambahkan lapisan lain ke berlian, untuk mendapatkan sesuatu seperti ini:

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

Tidak ada kelas yang mewarisi secara virtual, semua mewarisi secara publik. Kelas D21 dan D22 kemudian akan menyembunyikan fungsi virtual f () yang ambigu untuk DD, mungkin dengan mendeklarasikan fungsi privat. Mereka masing-masing mendefinisikan fungsi wrapper, masing-masing f1 () dan f2 (), masing-masing memanggil kelas-lokal (pribadi) f (), sehingga menyelesaikan konflik. Panggilan kelas DD f1 () jika ingin D11 :: f () dan f2 () jika ingin D12 :: f (). Jika Anda mendefinisikan pembungkus inline Anda mungkin akan mendapatkan sekitar nol overhead.

Tentu saja, jika Anda dapat mengubah D11 dan D12 maka Anda dapat melakukan trik yang sama di dalam kelas-kelas ini, tetapi seringkali itu tidak terjadi.


2
Ini bukan masalah lebih atau kurang elegan atau menyelesaikan ambiguitas (Anda selalu dapat menggunakan spesifikasi xxx :: eksplisit untuk itu). Dengan pewarisan non-virtual, setiap instance dari kelas DD memiliki dua instance independen B. Segera setelah kelas memiliki anggota data non-statis tunggal, pewarisan virtual dan non-virtual berbeda lebih dari sekadar sintaks.
user3489112

@ user3489112 Begitu ... tidak ada. Warisan virtual dan non virtual berbeda secara semantik, titik.
curiousguy


1

Anda sedikit membingungkan. Saya tidak tahu jika Anda mencampur beberapa konsep.

Anda tidak memiliki kelas dasar virtual di OP Anda. Anda hanya memiliki kelas dasar.

Anda melakukan warisan virtual. Ini biasanya digunakan dalam multiple inheritance sehingga beberapa kelas turunan menggunakan anggota kelas dasar tanpa mereproduksi mereka.

Kelas dasar dengan fungsi virtual murni tidak dapat dipakai. ini membutuhkan sintaksis yang dimiliki Paulus. Ini biasanya digunakan sehingga kelas turunan harus mendefinisikan fungsi-fungsi tersebut.

Saya tidak ingin menjelaskan lagi tentang ini karena saya tidak sepenuhnya mendapatkan apa yang Anda minta.


1
"Kelas dasar" yang digunakan dalam warisan virtual menjadi "kelas dasar virtual" (dalam konteks warisan yang tepat).
Luc Hermitte

1

Ini berarti panggilan ke fungsi virtual akan diteruskan ke kelas "benar".

C ++ FAQ Lite FTW.

Singkatnya, ini sering digunakan dalam skenario multiple-inheritance, di mana hierarki "berlian" terbentuk. Warisan virtual kemudian akan memecah ambiguitas yang dibuat di kelas bawah, ketika Anda memanggil fungsi di kelas itu dan fungsi harus diselesaikan ke kelas D1 atau D2 di atas kelas bawah itu. Lihat item FAQ untuk diagram dan detailnya.

Ini juga digunakan dalam delegasi saudari , fitur yang kuat (meskipun tidak untuk yang lemah hati). Lihat ini FAQ .

Juga lihat Butir 40 dalam Efektif C ++ edisi ke-3 (43 dalam edisi ke-2).


1

Contoh penggunaan runnable warisan berlian

Contoh ini menunjukkan bagaimana menggunakan kelas dasar virtual dalam skenario tipikal: untuk menyelesaikan warisan berlian.

#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);dari fungsi utama memberi saya kesalahan kompilasi: aDefault is not a member of Amenggunakan gcc 5.4.0. Apa yang harus dilakukan?
SebNag

@ Sedu ah terima kasih, hanya sesuatu yang saya lupa hapus dari copy paste, hapus sekarang. Contoh itu harus tetap bermakna tanpa itu.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

0

Kelas virtual tidak sama dengan warisan virtual. Kelas virtual yang tidak dapat Anda instantiate, virtual inheritance adalah sesuatu yang sepenuhnya berbeda.

Wikipedia menggambarkannya lebih baik daripada yang saya bisa. http://en.wikipedia.org/wiki/Virtual_inheritance


6
Tidak ada yang namanya "kelas virtual" di C ++. Namun ada "kelas dasar virtual" yang "virtual" berkenaan dengan warisan yang diberikan. Apa yang Anda referensikan adalah apa yang secara resmi disebut "kelas abstrak".
Luc Hermitte

@ LucHermitte, pasti ada kelas virtual di C ++. Periksa ini: en.wikipedia.org/wiki/Virtual_class .
Rafid

"error: 'virtual' hanya dapat ditentukan untuk fungsi". Saya tidak tahu bahasa apa ini. Tapi secara definitif tidak ada yang namanya kelas virtual dalam C ++.
Luc Hermitte

0

Warisan Biasa

Dengan 3 level non-diamond non-virtual-inheritance inheritance, ketika Anda membuat instance objek paling baru, baru dipanggil dan ukuran yang diperlukan untuk objek diselesaikan dari tipe kelas oleh kompiler dan diteruskan ke yang baru.

baru memiliki tanda tangan:

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

Dan membuat panggilan ke malloc , mengembalikan pointer kosong

Ini kemudian diteruskan ke konstruktor dari objek yang paling diturunkan, yang akan segera memanggil konstruktor tengah dan kemudian konstruktor tengah akan segera memanggil konstruktor dasar. Basis kemudian menyimpan pointer ke tabel virtualnya di awal objek dan kemudian atributnya setelah itu. Ini kemudian kembali ke konstruktor tengah yang akan menyimpan pointer tabel virtualnya di lokasi yang sama dan kemudian atributnya setelah atribut yang akan disimpan oleh konstruktor dasar. Ini mengembalikan ke konstruktor yang paling diturunkan, yang menyimpan pointer ke tabel virtualnya di lokasi yang sama dan kemudian atributnya setelah atribut yang akan disimpan oleh konstruktor tengah.

Karena pointer tabel virtual ditimpa, pointer tabel virtual akhirnya selalu menjadi salah satu kelas yang paling diturunkan. Virtualness merambat ke kelas yang paling diturunkan jadi jika suatu fungsi virtual di kelas menengah, itu akan menjadi virtual di kelas yang paling diturunkan tetapi bukan kelas dasar. Jika Anda secara polimorfik membuat instance dari kelas yang paling diturunkan ke pointer ke kelas dasar maka kompiler tidak akan menyelesaikan ini untuk panggilan tidak langsung ke tabel virtual dan sebagai gantinya akan memanggil fungsi secara langsung A::function(). Jika suatu fungsi adalah virtual untuk tipe yang Anda gunakan, maka ia akan memutuskan panggilan ke tabel virtual yang akan selalu menjadi kelas yang paling diturunkan. Jika ini bukan virtual untuk jenis itu maka itu hanya akan memanggilType::function() dan meneruskan pointer objek ke sana, cor ke Type.

Sebenarnya ketika saya mengatakan pointer ke tabel virtualnya, itu sebenarnya selalu merupakan offset 16 ke dalam tabel virtual.

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

virtualtidak diperlukan lagi di kelas yang lebih diturunkan jika itu virtual di kelas yang lebih rendah karena menyebar. Tetapi dapat digunakan untuk menunjukkan bahwa fungsi tersebut memang merupakan fungsi virtual, tanpa harus memeriksa kelas-kelas yang diwarisi tipe definisi.

override adalah penjaga kompiler lain yang mengatakan bahwa fungsi ini mengesampingkan sesuatu dan jika tidak maka melemparkan kesalahan kompilator.

= 0 berarti bahwa ini adalah fungsi abstrak

final mencegah fungsi virtual diimplementasikan lagi di kelas yang lebih diturunkan dan akan memastikan bahwa tabel virtual dari kelas yang paling diturunkan berisi fungsi akhir dari kelas itu.

= default membuatnya eksplisit dalam dokumentasi bahwa kompiler akan menggunakan implementasi default

= delete berikan kesalahan penyusun jika panggilan ke ini dilakukan

Warisan Virtual

Mempertimbangkan

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();
 }

Tanpa mewarisi kelas bass Anda akan mendapatkan objek yang terlihat seperti ini:

Alih-alih ini:

Yaitu akan ada 2 objek dasar.

Dalam situasi warisan berlian virtual di atas, setelah baru dipanggil, ia memanggil konstruktor yang paling diturunkan dan dalam konstruktor itu, ia memanggil semua 3 konstruktor turunan yang melewati offset ke dalam tabel tabel virtualnya, alih-alih memanggil hanya memanggil DerivedClass1::DerivedClass1()danDerivedClass2::DerivedClass2() kemudian mereka berdua callingBase::Base()

Berikut ini semua dikompilasi dalam mode debug -O0 sehingga akan ada perakitan yang berlebihan

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

Itu panggilan Base::Base()dengan pointer ke objek offset 32. Base menyimpan pointer ke tabel virtualnya di alamat yang diterimanya dan anggota-anggotanya setelah itu.

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()kemudian memanggil DerivedClass1::DerivedClass1()dengan pointer ke objek offset 0 dan juga melewati alamatVTT 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()kemudian melewati alamat dari objek + 16 dan alamat VTT untuk DerivedDerivedClass+24untuk DerivedClass2::DerivedClass2()yang perakitan identik dengan DerivedClass1::DerivedClass1()kecuali untuk garis mov DWORD PTR [rax+8], 3yang jelas memiliki 4 bukannya 3 untukd = 4 .

Setelah ini, ia mengganti semua 3 pointer tabel virtual dalam objek dengan pointer ke offset di DerivedDerivedClassvtable ke representasi untuk kelas itu.

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
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.