Quero saber o que é uma " classe base virtual " e o que isso significa.
Deixe-me mostrar um exemplo:
class Foo
{
public:
void DoSomething() { /* ... */ }
};
class Bar : public virtual Foo
{
public:
void DoSpecific() { /* ... */ }
};
Quero saber o que é uma " classe base virtual " e o que isso significa.
Deixe-me mostrar um exemplo:
class Foo
{
public:
void DoSomething() { /* ... */ }
};
class Bar : public virtual Foo
{
public:
void DoSpecific() { /* ... */ }
};
Respostas:
As classes base virtuais, usadas na herança virtual, são uma maneira de impedir que várias "instâncias" de uma determinada classe apareçam em uma hierarquia de herança ao usar a herança múltipla.
Considere o seguinte cenário:
class A { public: void Foo() {} };
class B : public A {};
class C : public A {};
class D : public B, public C {};
A hierarquia de classes acima resulta no "diamante temido", que se parece com isso:
A
/ \
B C
\ /
D
Uma instância de D será composta de B, que inclui A e C, que também inclui A. Portanto, você tem duas "instâncias" (por falta de uma melhor expressão) de A.
Quando você tem esse cenário, você tem a possibilidade de ambiguidade. O que acontece quando você faz isso:
D d;
d.Foo(); // is this B's Foo() or C's Foo() ??
Herança virtual existe para resolver esse problema. Ao especificar virtual ao herdar suas classes, você está dizendo ao compilador que deseja apenas uma única instância.
class A { public: void Foo() {} };
class B : public virtual A {};
class C : public virtual A {};
class D : public B, public C {};
Isso significa que há apenas uma "instância" de A incluída na hierarquia. Conseqüentemente
D d;
d.Foo(); // no longer ambiguous
Este é um mini resumo. Para mais informações, leia isto e isto . Um bom exemplo também está disponível aqui .
virtual
, o layout do objeto se parecerá com o diamante; e se não usar virtual
, em seguida, os olhares de layout objeto como uma estrutura de árvore que contém dois A
s
Como observação lateral, o problema com o Dreaded Diamond é que a classe base está presente várias vezes. Portanto, com herança regular, você acredita que possui:
A
/ \
B C
\ /
D
Mas no layout da memória, você tem:
A A
| |
B C
\ /
D
Isso explica por que, quando ligar D::foo()
, você tem um problema de ambiguidade. Mas o verdadeiro problema surge quando você deseja usar uma variável membro de A
. Por exemplo, digamos que temos:
class A
{
public :
foo() ;
int m_iValue ;
} ;
Quando você tentar acessar m_iValue
a partir D
, o compilador irá protestar, porque na hierarquia, vai ver dois m_iValue
, não um. E se você modificar um, digamos, B::m_iValue
(que é o A::m_iValue
pai de B
), C::m_iValue
não será modificado (que é o A::m_iValue
pai deC
).
É aqui que a herança virtual se torna útil; assim, você voltará a um verdadeiro layout de diamante, não apenas com um foo()
método, mas também com um e apenas um m_iValue
.
Imagine:
A
tem alguma característica básica.B
adiciona a ele algum tipo de matriz legal de dados (por exemplo)C
adiciona a ele alguns recursos interessantes, como um padrão de observador (por exemplo, em m_iValue
).D
herda de B
e C
, e assim de A
.Com herança normal, a modificação m_iValue
de D
é ambígua e isso deve ser resolvido. Mesmo que seja, existem dois m_iValues
dentroD
, então é melhor você se lembrar disso e atualizar os dois ao mesmo tempo.
Com herança virtual, modificar m_iValue
de D
está ok ... Mas ... Digamos que você tenha D
. Através de sua C
interface, você anexou um observador. E através de sua B
interface, você atualiza a matriz legal, que tem o efeito colateral de alterar diretamente m_iValue
...
Como a alteração m_iValue
é feita diretamente (sem usar um método de acessador virtual), o observador "ouvindo" C
não será chamado, porque o código que implementa a escuta está dentro C
eB
não sabe sobre isso ...
Se você tem um diamante em sua hierarquia, significa que você tem 95% de probabilidade de ter feito algo errado com essa hierarquia.
Explicar a herança múltipla com bases virtuais requer um conhecimento do modelo de objeto C ++. E a explicação clara do tópico é melhor feita em um artigo e não em uma caixa de comentários.
A melhor e legível explicação que descobri que resolveu todas as minhas dúvidas sobre esse assunto foi este artigo: http://www.phpcompiler.org/articles/virtualinheritance.html
Você realmente não precisará ler mais nada sobre o assunto (a menos que seja um escritor de compilador) depois de ler isso ...
Uma classe base virtual é uma classe que não pode ser instanciada: você não pode criar objetos diretos a partir dela.
Eu acho que você está confundindo duas coisas muito diferentes. Herança virtual não é a mesma coisa que uma classe abstrata. A herança virtual modifica o comportamento das chamadas de função; às vezes, resolve chamadas de função que seriam ambíguas; às vezes, adia o tratamento de chamadas de função para uma classe diferente daquela que seria de esperar em uma herança não virtual.
Gostaria de acrescentar os esclarecimentos gentis de OJ.
A herança virtual não vem sem preço. Como em todas as coisas virtuais, você obtém um desempenho atingido. Existe uma maneira de contornar esse problema de desempenho que é possivelmente menos elegante.
Em vez de quebrar o diamante derivando virtualmente, você pode adicionar outra camada ao diamante, para obter algo assim:
B
/ \
D11 D12
| |
D21 D22
\ /
DD
Nenhuma das classes herda virtualmente, todas herdam publicamente. As classes D21 e D22 ocultarão a função virtual f (), que é ambígua para DD, talvez declarando a função privada. Cada um deles define uma função de wrapper, f1 () e f2 (), respectivamente, cada um chamando classe-local (privado) f (), resolvendo conflitos. A classe DD chama f1 () se desejar D11 :: f () e f2 () se desejar D12 :: f (). Se você definir os wrappers em linha, provavelmente terá uma sobrecarga zero.
Obviamente, se você pode alterar o D11 e o D12, pode fazer o mesmo truque nessas classes, mas geralmente esse não é o caso.
Além do que já foi dito sobre heranças múltiplas e virtuais, há um artigo muito interessante no Diário do Dr. Dobb: Herança Múltipla Considerada Útil
Você está sendo um pouco confuso. Não sei se você está misturando alguns conceitos.
Você não tem uma classe base virtual no seu OP. Você apenas tem uma classe base.
Você fez herança virtual. Isso geralmente é usado em herança múltipla, para que várias classes derivadas usem os membros da classe base sem reproduzi-los.
Uma classe base com uma função virtual pura não é instanciada. isso requer a sintaxe em que Paulo chega. É normalmente usado para que as classes derivadas definam essas funções.
Não quero explicar mais sobre isso porque não entendo totalmente o que você está perguntando.
Isso significa que uma chamada para uma função virtual será encaminhada para a classe "certa".
C ++ FAQ Lite FTW.
Em resumo, é frequentemente usado em cenários de herança múltipla, onde uma hierarquia de "diamantes" é formada. A herança virtual quebrará a ambiguidade criada na classe inferior, quando você chamar a função nessa classe e a função precisar ser resolvida para a classe D1 ou D2 acima dessa classe inferior. Veja o item FAQ para um diagrama e detalhes.
Também é usado na delegação irmã , um recurso poderoso (embora não seja para os fracos de coração). Veja este FAQ.
Veja também o Item 40 na 3ª edição Efetiva do C ++ (43 na 2ª edição).
Exemplo de uso executável de herança de diamante
Este exemplo mostra como usar uma classe base virtual no cenário típico: para resolver a herança de diamantes.
#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);
da função principal me dá um erro de compilação: aDefault is not a member of A
usando o gcc 5.4.0. O que é suposto fazer?
Classes virtuais não são iguais a herança virtual. Classes virtuais que você não pode instanciar; herança virtual é algo totalmente diferente.
A Wikipedia descreve melhor do que eu. http://en.wikipedia.org/wiki/Virtual_inheritance
Com a herança típica de herança não virtual de três níveis, sem diamante, quando você instancia um novo objeto mais derivado, new é chamado e o tamanho necessário para o objeto é resolvido do tipo de classe pelo compilador e passado para novo.
new tem uma assinatura:
_GLIBCXX_WEAK_DEFINITION void *
operator new (std::size_t sz) _GLIBCXX_THROW (std::bad_alloc)
E faz uma chamada para malloc
, retornando o ponteiro nulo
Isso é passado ao construtor do objeto mais derivado, que chamará imediatamente o construtor intermediário e, em seguida, o construtor intermediário chamará imediatamente o construtor base. A base então armazena um ponteiro para sua tabela virtual no início do objeto e, em seguida, seus atributos após ele. Isso retorna ao construtor do meio que armazenará seu ponteiro de tabela virtual no mesmo local e, em seguida, seus atributos após os atributos que teriam sido armazenados pelo construtor de base. Ele retorna ao construtor mais derivado, que armazena um ponteiro para sua tabela virtual no mesmo local e, em seguida, seus atributos após os atributos que teriam sido armazenados pelo construtor intermediário.
Como o ponteiro da tabela virtual é substituído, o ponteiro da tabela virtual acaba sempre sendo o da classe mais derivada. A virtualidade se propaga para a classe mais derivada; portanto, se uma função for virtual na classe média, ela será virtual na classe mais derivada, mas não na classe base. Se você polimorficamente converter uma instância da classe mais derivada em um ponteiro para a classe base, o compilador não resolverá isso para uma chamada indireta para a tabela virtual e, em vez disso, chamará a função diretamente A::function()
. Se uma função é virtual para o tipo para o qual você a lançou, ela será resolvida para uma chamada na tabela virtual, que sempre será a da classe mais derivada. Se não for virtual para esse tipo, ele simplesmente chamará Type::function()
e passará o ponteiro do objeto para ele, convertido em Type.
Na verdade, quando digo ponteiro para sua tabela virtual, na verdade é sempre um deslocamento de 16 na tabela 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
virtual
não é necessário novamente em classes mais derivadas se for virtual em uma classe menos derivada porque se propaga. Mas pode ser usado para mostrar que a função é realmente uma função virtual, sem precisar verificar as classes que herda as definições de tipo.
override
é outro guarda do compilador que diz que esta função está substituindo algo e, se não estiver, gera um erro do compilador.
= 0
significa que esta é uma função abstrata
final
impede que uma função virtual seja implementada novamente em uma classe mais derivada e assegura que a tabela virtual da classe mais derivada contenha a função final dessa classe.
= default
torna explícito na documentação que o compilador usará a implementação padrão
= delete
dê um erro de compilador se uma chamada para isso for tentada
Considerar
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();
}
Sem virtualmente herdar a classe de baixo, você obterá um objeto parecido com este:
Em vez disso:
Ou seja, haverá 2 objetos de base.
Na situação herança diamante virtuais acima, após novo é chamado, ele chama o construtor mais derivado e nesse construtor, ele chama todos os 3 construtores derivados passando compensações em sua tabela de mesa virtual, em vez de chamar apenas chamando DerivedClass1::DerivedClass1()
eDerivedClass2::DerivedClass2()
em seguida os dois chamadaBase::Base()
O seguinte é todo compilado no modo de depuração -O0, para que haja um assembly redundante
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
Ele chama Base::Base()
com um ponteiro para o deslocamento 32 do objeto. A base armazena um ponteiro para sua tabela virtual no endereço que ele recebe e seus membros depois dele.
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()
depois chama DerivedClass1::DerivedClass1()
com um ponteiro para o deslocamento 0 do objeto e também passa o endereço deVTT 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()
depois passa o endereço do objeto + 16 e o endereço da VTT para DerivedDerivedClass+24
para DerivedClass2::DerivedClass2()
cuja montagem é idêntica, DerivedClass1::DerivedClass1()
exceto para a linha mov DWORD PTR [rax+8], 3
que obviamente possui 4 em vez de 3 para d = 4
.
Depois disso, ele substitui todos os três ponteiros de tabela virtual no objeto por ponteiros para compensar a tabela de DerivedDerivedClass
v na representação da classe.
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