Я хочу знати, що таке " віртуальний базовий клас " і що це означає.
Дозвольте мені показати приклад:
class Foo
{
public:
void DoSomething() { /* ... */ }
};
class Bar : public virtual Foo
{
public:
void DoSpecific() { /* ... */ }
};
Я хочу знати, що таке " віртуальний базовий клас " і що це означає.
Дозвольте мені показати приклад:
class Foo
{
public:
void DoSomething() { /* ... */ }
};
class Bar : public virtual Foo
{
public:
void DoSpecific() { /* ... */ }
};
Відповіді:
Віртуальні базові класи, що використовуються у віртуальному успадкуванні, є способом запобігання появи декількох "екземплярів" даного класу, що з'являються в ієрархії спадкування при використанні множинного успадкування.
Розглянемо наступний сценарій:
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
Це міні-резюме. Для отримання додаткової інформації прочитайте це та це . Хороший приклад також доступний тут .
virtual
, то макет об'єкта виглядає як алмаз; а якщо ми не використовуємо, virtual
то макет об’єкта виглядає як деревоподібна структура, яка містить два A
s
Як бічна примітка, проблема з 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% ймовірність зробити щось не так із вказаною ієрархією.
Пояснення множинного успадкування віртуальними базами вимагає знання об'єктної моделі C ++. А пояснення теми чітко найкраще робити у статті, а не у вікні коментарів.
Найкраще, читабельне пояснення, яке я знайшов, що вирішило всі мої сумніви з цього приводу, була ця стаття: http://www.phpcompiler.org/articles/virtualinheritance.html
Вам не потрібно буде читати нічого іншого по темі (якщо ви не є автором укладача), прочитавши це ...
Віртуальний базовий клас - це клас, який неможливо створити інстанціювати: ви не можете створити з нього прямий об’єкт.
Я думаю, що ви плутаєте дві дуже різні речі. Віртуальна спадщина - це не те саме, що абстрактний клас. Віртуальне успадкування змінює поведінку функціональних викликів; іноді він вирішує виклики функцій, що в іншому випадку було б неоднозначним, іноді відкладає обробку функціональних викликів класу, відмінному від того, якого можна було б очікувати у невіртуальному спадкуванні.
Я хотів би додати до свого роду роз'яснення OJ.
Віртуальна спадщина не виходить без ціни. Як і у всіх віртуальних речах, ви отримуєте хіт на продуктивність. Існує спосіб цього хіта, який може бути менш елегантним.
Замість того, щоб розбити алмаз шляхом виведення віртуально, ви можете додати ще один шар до алмазу, щоб отримати щось подібне:
B
/ \
D11 D12
| |
D21 D22
\ /
DD
Жоден з класів не успадковує практично, всі успадковують публічно. Класи D21 і D22 потім приховатимуть віртуальну функцію f (), яка є неоднозначною для DD, можливо, оголосивши функцію приватною. Кожен би визначав функцію обгортки, f1 () і f2 () відповідно, кожен виклик класу-локальний (приватний) f (), вирішуючи таким чином конфлікти. Клас DD викликає f1 (), якщо він хоче D11 :: f () і f2 (), якщо він хоче D12 :: f (). Якщо ви визначите фантики вбудованими, ви, ймовірно, отримаєте приблизно нульові накладні витрати.
Звичайно, якщо ви можете змінити D11 і D12, то ви можете зробити такий же трюк всередині цих класів, але часто це не так.
На додаток до того, що вже було сказано про множинне та віртуальне успадкування, є ще дуже цікава стаття в журналі "Д-р Добб": Множинне спадкування вважається корисним
Ти трохи плутаєшся. Я не знаю, чи ви змішуєте якісь поняття.
У вас немає віртуального базового класу у вашій ОП. У вас просто базовий клас.
Ви зробили віртуальну спадщину. Зазвичай це використовується при множинному успадкуванні, так що кілька похідних класів використовують члени базового класу, не відтворюючи їх.
Базовий клас з чисто віртуальною функцією не може бути примірник. для цього потрібен синтаксис, до якого потрапляє Пол. Зазвичай використовується так, що похідні класи повинні визначати ці функції.
Я не хочу більше пояснювати це, тому що я абсолютно не розумію, про що ви просите.
Це означає, що виклик віртуальної функції буде переадресований у "правильний" клас.
C ++ FAQ Lite FTW.
Коротше кажучи, його часто використовують у сценаріях множинного успадкування, де формується "алмазна" ієрархія. Тоді віртуальне успадкування порушить неоднозначність, створену в нижньому класі, коли ви викликаєте функцію в цьому класі, і функцію потрібно вирішити на клас D1 або D2 вище цього нижнього класу. Дивіться пункт FAQ для діаграми та деталей.
Він також використовується в сестринській делегації , потужною особливістю (хоча і не для слабкого серця). Дивіться це поширене запитання.
Також див. Пункт 40 у "Ефективному C ++" 3-го видання (43 у другому виданні).
Приклад використання алгоритму успадкування алмазів
Цей приклад показує, як використовувати віртуальний базовий клас у типовому сценарії: для вирішення спадкування алмазів.
#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);
}
assert(A::aDefault == 0);
від основної функції дає мені помилку компіляції: aDefault is not a member of A
використання gcc 5.4.0. Що це робити?
Віртуальних класів немає те саме, що віртуальне успадкування. Віртуальні класи, які ви не можете створити, віртуальна спадщина - це зовсім інше.
Вікіпедія описує це краще, ніж я можу. http://en.wikipedia.org/wiki/Virtual_inheritance
З типовим недіамантовим успадковуванням без віртуального 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 віртуальні вказівники таблиці в об’єкті на покажчики на зміщення в DerivedDerivedClass
v 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