Я хочу знать, что такое " виртуальный базовый класс" » и что это значит.
Позвольте мне показать пример:
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. Таким образом, у вас есть два «экземпляра» (для лучшего выражения) из A.
Когда у вас есть этот сценарий, у вас есть возможность двусмысленности. Что происходит, когда вы делаете это:
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. следовательно
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 () соответственно, каждый из которых вызывал бы локальный класс (private) f (), таким образом разрешая конфликты. Класс DD вызывает f1 (), если он хочет D11 :: f () и f2 (), если он хочет D12 :: f (). Если вы определите встроенные оболочки, вы, вероятно, получите около нуля накладных расходов.
Конечно, если вы можете изменить D11 и D12, вы можете сделать один и тот же трюк внутри этих классов, но часто это не так.
В дополнение к тому, что уже было сказано о множественном и виртуальном наследовании, в журнале д-ра Добба есть очень интересная статья: множественное наследование считается полезным
Ты немного запутался. Я не знаю, смешиваете ли вы некоторые понятия.
У вас нет виртуального базового класса в вашем OP. У вас просто есть базовый класс.
Вы сделали виртуальное наследование. Это обычно используется в множественном наследовании, так что несколько производных классов используют члены базового класса, не воспроизводя их.
Базовый класс с чисто виртуальной функцией не создается. для этого требуется синтаксис, который использует Пол. Обычно он используется для того, чтобы производные классы определяли эти функции.
Я не хочу больше об этом объяснять, потому что я не совсем понимаю, что вы спрашиваете.
Это означает, что вызов виртуальной функции будет перенаправлен в «правильный» класс.
C ++ FAQ Lite FTW.
Короче говоря, он часто используется в сценариях множественного наследования, где формируется «алмазная» иерархия. Виртуальное наследование разрушит неоднозначность, созданную в нижнем классе, когда вы вызываете функцию в этом классе, и функция должна быть разрешена либо в класс D1, либо в D2 выше этого нижнего класса. См. Пункт часто задаваемых вопросов для диаграммы и деталей.
Он также используется в сестринском делегировании , мощная функция (хотя и не для слабонервных). Смотрите этот FAQ.
Также см. Пункт 40 в Effective C ++ 3-е издание (43 в 2-е издание).
Пример использования бриллиантового наследования
В этом примере показано, как использовать виртуальный базовый класс в типичном сценарии: для решения проблемы наследования алмазов.
#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()
и передаст на него указатель объекта, приведенный к типу.
На самом деле, когда я говорю указатель на его виртуальную таблицу, на самом деле это всегда смещение 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 базовых объекта.
В описанной выше ситуации виртуального наследования алмазов после вызова new он вызывает самый производный конструктор, и в этом конструкторе он вызывает все 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+24
к DerivedClass2::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