Що стосується C ++, що таке віртуальний базовий клас?


403

Я хочу знати, що таке " віртуальний базовий клас " і що це означає.

Дозвольте мені показати приклад:

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

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

Чи повинні ми використовувати віртуальні базові класи у "множинному успадкуванні", тому що якщо клас A має змінну члена int a, а клас B також має член int a, а клас c успадковує клас A і B, як ми вирішимо, який "a" використовувати?
Наміт Сінья

2
@NamitSinha ні, віртуальна спадщина не вирішує цю проблему. Учасник a все одно був би неоднозначним
Іхтіо

@NamitSinha Віртуальне успадкування не є магічним інструментом для усунення кількох неясностей, пов’язаних із спадщиною. Він "вирішує" "проблему" мати непряму базу не один раз. Що є лише проблемою, якщо вона мала бути спільною (часто, але не завжди).
цікавогут

Відповіді:


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 буде складатися з B, який включає A, і C, який також включає A. Отже, у вас є два "екземпляри" (для кращого вираження) А.

Коли у вас є такий сценарій, у вас є можливість неоднозначності. Що відбувається при цьому:

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

Віртуальна спадщина існує для вирішення цієї проблеми. Коли ви вказуєте віртуальний під час успадкування своїх класів, ви повідомляєте компілятору, що вам потрібно лише один екземпляр.

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

Це означає, що в ієрархії є лише один "екземпляр" A. Звідси

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

Це міні-резюме. Для отримання додаткової інформації прочитайте це та це . Хороший приклад також доступний тут .


7
@Bohdan ні, це не :)
ОВ.

6
@OJ. чому ні? Вони веселі :)
Богдан

15
@Bohdan використовує віртуальне ключове слово стільки ж менше, оскільки коли ми використовуємо віртуальне ключове слово, застосовується важкий механізм ваги. Отже, ефективність вашої програми буде знижена.
Сагар

73
Ваша діаграма "страшний діамант" заплутана, хоча, здається, зазвичай використовується. Це насправді схема, що показує відносини успадкування класів - не макет об'єкта. Конфузна частина полягає в тому, що якщо ми все-таки використовуємо virtual, то макет об'єкта виглядає як алмаз; а якщо ми не використовуємо, virtualто макет об’єкта виглядає як деревоподібна структура, яка містить два As
MM

5
Я маю спростувати цю відповідь з причини, викладеної М.М. - діаграма виражає протилежне до повідомлення.
Девід Стоун

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компілятор буде протестувати, тому що в ієрархії, то побачить два m_iValue, а не один. І якщо ви модифікуєте його, скажімо, B::m_iValue(це є A::m_iValueбатьком B), воно C::m_iValueне буде змінено (тобто A::m_iValueбатьківське C).

Ось тут віртуальна спадщина стає корисною, як і з нею, ви повернетесь до справжнього алмазного макета, використовуючи не лише один foo()метод, але і один і єдиний m_iValue.

Що може піти не так?

Уявіть собі:

  • A має деяку основну особливість.
  • B додає до нього якийсь класний масив даних (наприклад)
  • Cдодає до нього якусь цікаву особливість, як зразок спостерігача (наприклад, на m_iValue).
  • Dуспадковує від Bі C, і таким чином від A.

При нормальному успадкуванні модифікація m_iValueз боку Dнеоднозначна, і це потрібно вирішити. Навіть якщо він є, m_iValuesвсередині є дваD , тож вам краще запам'ятати це та оновлювати їх одночасно.

З віртуальним успадкуванням, модифікація m_iValueз D- це нормально ... Але ... Скажімо, у вас є D. Через його Cінтерфейс ви підключили спостерігача. І через його Bінтерфейс ви оновлюєте класний масив, який має побічний ефект безпосередньо зміниm_iValue ...

Оскільки зміна m_iValueздійснюється безпосередньо (без використання методу віртуального доступу), спостерігач "прослуховування" наскрізь Cне буде викликаний, тому що код, що реалізує прослуховування, є C, іB він не знає про це ...

Висновок

Якщо у вас є діамант у вашій ієрархії, це означає, що ви маєте 95% ймовірність зробити щось не так із вказаною ієрархією.


Ваше "що може піти не так" пов'язано з прямим доступом до базового члена, а не через багаторазове успадкування. Позбавтеся "B" і у вас однакова проблема. Основне правило: "якщо воно не приватне, воно повинно бути віртуальним", це дозволяє уникнути проблеми. M_iValue не є віртуальною, а тому має бути приватною
Кріс Додд

4
@Chris Dodd: Не зовсім так. Те, що відбувається з m_iValue, трапилося б з будь-яким символом ( наприклад, typedef, змінною члена, функцією члена, переданою базовому класу тощо ). Це дійсно проблема багатократного успадкування, проблема, щодо якої користувачі повинні знати, що правильно використовувати декілька спадків, замість того, щоб піти шляхом Java і зробити висновок "Множинне успадкування - це 100% зло, давайте зробимо це з інтерфейсами".
paercebal

Привіт, Коли ми використовуємо віртуальне ключове слово, буде лише одна копія А. Моє запитання - як ми можемо знати, чи походить він від B або C? Чи моє питання взагалі справедливе?
користувач875036

@ user875036: A походить як від B, так і від C. Віртуальність дещо змінює (наприклад, D буде називати конструктор A, а не B, C). І B, і C (і D) мають вказівник на A.
paercebal

3
FWIW, якщо хтось цікавиться, змінні учасника не можуть бути віртуальними - віртуальна є специфікатором функцій . ТАК посилання: stackoverflow.com/questions/3698831/…
rholmes

34

Пояснення множинного успадкування віртуальними базами вимагає знання об'єктної моделі C ++. А пояснення теми чітко найкраще робити у статті, а не у вікні коментарів.

Найкраще, читабельне пояснення, яке я знайшов, що вирішило всі мої сумніви з цього приводу, була ця стаття: http://www.phpcompiler.org/articles/virtualinheritance.html

Вам не потрібно буде читати нічого іншого по темі (якщо ви не є автором укладача), прочитавши це ...


10

Віртуальний базовий клас - це клас, який неможливо створити інстанціювати: ви не можете створити з нього прямий об’єкт.

Я думаю, що ви плутаєте дві дуже різні речі. Віртуальна спадщина - це не те саме, що абстрактний клас. Віртуальне успадкування змінює поведінку функціональних викликів; іноді він вирішує виклики функцій, що в іншому випадку було б неоднозначним, іноді відкладає обробку функціональних викликів класу, відмінному від того, якого можна було б очікувати у невіртуальному спадкуванні.


7

Я хотів би додати до свого роду роз'яснення OJ.

Віртуальна спадщина не виходить без ціни. Як і у всіх віртуальних речах, ви отримуєте хіт на продуктивність. Існує спосіб цього хіта, який може бути менш елегантним.

Замість того, щоб розбити алмаз шляхом виведення віртуально, ви можете додати ще один шар до алмазу, щоб отримати щось подібне:

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

Жоден з класів не успадковує практично, всі успадковують публічно. Класи D21 і D22 потім приховатимуть віртуальну функцію f (), яка є неоднозначною для DD, можливо, оголосивши функцію приватною. Кожен би визначав функцію обгортки, f1 () і f2 () відповідно, кожен виклик класу-локальний (приватний) f (), вирішуючи таким чином конфлікти. Клас DD викликає f1 (), якщо він хоче D11 :: f () і f2 (), якщо він хоче D12 :: f (). Якщо ви визначите фантики вбудованими, ви, ймовірно, отримаєте приблизно нульові накладні витрати.

Звичайно, якщо ви можете змінити D11 і D12, то ви можете зробити такий же трюк всередині цих класів, але часто це не так.


2
Це не питання більш-менш елегантного або вирішення двозначностей (ви завжди можете використовувати явні xxx :: специфікації для цього). Що стосується невіртуального успадкування, кожен екземпляр класу DD має два незалежні екземпляри B. Як тільки у класу є один нестатичний член даних, віртуальне та невіртуальне успадкування відрізняються більш ніж просто синтаксисом.
користувач3489112

@ user3489112 Як тільки ... нічого. Віртуальна та невіртуальна спадщина відрізняються семантично, періодом.
curiousguy


1

Ти трохи плутаєшся. Я не знаю, чи ви змішуєте якісь поняття.

У вас немає віртуального базового класу у вашій ОП. У вас просто базовий клас.

Ви зробили віртуальну спадщину. Зазвичай це використовується при множинному успадкуванні, так що кілька похідних класів використовують члени базового класу, не відтворюючи їх.

Базовий клас з чисто віртуальною функцією не може бути примірник. для цього потрібен синтаксис, до якого потрапляє Пол. Зазвичай використовується так, що похідні класи повинні визначати ці функції.

Я не хочу більше пояснювати це, тому що я абсолютно не розумію, про що ви просите.


1
"Базовий клас", який використовується у віртуальному успадкуванні, стає "віртуальним базовим класом" (у контексті цього точного успадкування).
Люк Ермітт

1

Це означає, що виклик віртуальної функції буде переадресований у "правильний" клас.

C ++ FAQ Lite FTW.

Коротше кажучи, його часто використовують у сценаріях множинного успадкування, де формується "алмазна" ієрархія. Тоді віртуальне успадкування порушить неоднозначність, створену в нижньому класі, коли ви викликаєте функцію в цьому класі, і функцію потрібно вирішити на клас D1 або D2 вище цього нижнього класу. Дивіться пункт FAQ для діаграми та деталей.

Він також використовується в сестринській делегації , потужною особливістю (хоча і не для слабкого серця). Дивіться це поширене запитання.

Також див. Пункт 40 у "Ефективному C ++" 3-го видання (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 Aвикористання gcc 5.4.0. Що це робити?
SebNag

@SebTu ах спасибі, просто щось, що я забув видалити з копії пасти, видалив її зараз. Приклад все-таки повинен бути значущим без нього.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

0

Віртуальних класів немає те саме, що віртуальне успадкування. Віртуальні класи, які ви не можете створити, віртуальна спадщина - це зовсім інше.

Вікіпедія описує це краще, ніж я можу. http://en.wikipedia.org/wiki/Virtual_inheritance


6
У C ++ немає такого поняття, як "віртуальні класи". Однак існують "віртуальні базові класи", які є "віртуальними" щодо даної спадщини. На що ви посилаєтесь, це те, що офіційно називається "абстрактні класи".
Люк Ермітт

@LucHermitte, на C ++ виразно є віртуальні класи. Перевірте це: en.wikipedia.org/wiki/Virtual_class .
Рафід

"error: 'virtual' можна вказати лише для функцій". Я не знаю, що це за мова. Але на C ++ точно не існує такого поняття, як віртуальний клас .
Люк Ермітт

0

Регулярне спадкування

З типовим недіамантовим успадковуванням без віртуального 3 рівня, коли ви створюєте інстанцію нового найбільш похідного об'єкта, викликується новий, а розмір, необхідний для об'єкта, вирішується компілятором від типу класу та передається новому.

новий має підпис:

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

І телефонує до malloc , повертаючи недійсний покажчик

Потім він передається конструктору найбільш похідного об'єкта, який негайно викликає середній конструктор, а потім середній конструктор негайно викликає базовий конструктор. Потім база зберігає вказівник на свою віртуальну таблицю на початку об'єкта, а потім його атрибути після нього. Потім він повертається до середнього конструктора, який зберігатиме вказівник віртуальної таблиці в тому самому місці, а потім його атрибути після атрибутів, які були б збережені базовим конструктором. Він повертається до найбільш похідного конструктора, який зберігає вказівник на свою віртуальну таблицю в тому самому місці, а потім його атрибути після атрибутів, які були б збережені середнім конструктором.

Оскільки вказівник віртуальної таблиці перезаписаний, то вказівник віртуальної таблиці завжди є одним із найбільш похідних класів. Віртуальність поширюється на найбільш похідний клас, тому якщо функція є віртуальною в середньому класі, вона буде віртуальною в найбільш похідному класі, але не в базовому. Якщо ви поліморфно передаєте екземпляр найбільш похідного класу вказівник на базовий клас, компілятор не вирішить це до непрямого виклику до віртуальної таблиці, а замість цього викличе функцію безпосередньо 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 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+24до DerivedClass2::DerivedClass2()якої збірка ідентична DerivedClass1::DerivedClass1()за винятком того лінії , mov DWORD PTR [rax+8], 3які , очевидно , має 4 замість 3 для d = 4.

Після цього він замінює всі 3 віртуальні вказівники таблиці в об’єкті на покажчики на зміщення в DerivedDerivedClassv 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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.