Em C ++, o que é uma classe base virtual?

403

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() { /* ... */ }
};
popopome
fonte
devemos usar classes base virtuais em 'herança múltipla' porque se a classe A possui a variável de membro int ae a classe B também tem o membro int ae a classe c herda as classes A e B, como decidimos qual 'a' usar?
Namit Sinha
2
@NamitSinha não, a herança virtual não resolve esse problema. O membro de uma seria de qualquer maneira ambígua
Ichthyo
A herança virtual do @NamitSinha não é uma ferramenta mágica para remover várias ambiguidades relacionadas à herança. Ele "resolve" um "problema" de ter uma base indireta mais de uma vez. O que é apenas um problema se se pretende compartilhar (geralmente, mas nem sempre).
curiousguy

Respostas:

533

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 .

JO.
fonte
7
@Bohdan não, não :)
JO.
6
@OJ. Por que não? Eles são hilariantes :)
Bohdan
15
O @Bohdan usa palavras-chave virtuais tanto quanto menos, porque quando usamos palavras-chave virtuais, um mecanismo pesado é aplicado. Portanto, a eficiência do seu programa será reduzida.
Sagar
73
Seu diagrama de "diamante temido" é confuso, embora pareça ser comumente usado. Na verdade, é um diagrama que mostra os relacionamentos de herança de classe - não um layout de objeto. A parte confusa é que, se usarmos 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 As
MM
5
Eu tenho que rebaixar esta resposta pelo motivo descrito por MM - o diagrama expressa o oposto do post.
18180 David Stone (
251

Sobre o layout da memória

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_iValuea 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_iValuepai de B), C::m_iValuenão será modificado (que é o A::m_iValuepai 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.

O que poderia dar errado?

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 ).
  • Dherda de Be C, e assim de A.

Com herança normal, a modificação m_iValuede Dé ambígua e isso deve ser resolvido. Mesmo que seja, existem dois m_iValuesdentroD , então é melhor você se lembrar disso e atualizar os dois ao mesmo tempo.

Com herança virtual, modificar m_iValuede Destá ok ... Mas ... Digamos que você tenha D. Através de sua Cinterface, você anexou um observador. E através de sua Binterface, 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" Cnão será chamado, porque o código que implementa a escuta está dentro CeB não sabe sobre isso ...

Conclusão

Se você tem um diamante em sua hierarquia, significa que você tem 95% de probabilidade de ter feito algo errado com essa hierarquia.

paercebal
fonte
Seu 'o que poderia dar errado' deve-se ao acesso direto a um membro de base, não devido à herança múltipla. Livrar-se de 'B" e você tem o mesmo problema regra básica de:. 'Se não for privado, deve ser virtual' evita o problema m_iValue não é virtual e por isso deve ser privado.
Chris Dodd
4
@ Chris Dodd: Não exatamente. O que acontece com m_iValue teria acontecido com qualquer símbolo ( por exemplo, typedef, variável de membro, função de membro, convertido para a classe base, etc. ). Este é realmente um problema de herança múltipla, um problema que os usuários devem estar cientes de usar a herança múltipla corretamente, em vez de seguir o caminho Java e concluir "A herança múltipla é 100% má, vamos fazer isso com interfaces".
paercebal
Olá, Quando usamos uma palavra-chave virtual, haverá apenas uma cópia de A. Minha pergunta é: como sabemos se é proveniente de B ou C? Minha pergunta é válida?
user875036
@ user875036: A vem de B e C. De fato, a virtualidade muda algumas coisas (por exemplo, D chamará o construtor de A, não B, nem C). B e C (e D) têm um ponteiro para A.
paercebal
3
FWIW, no caso de alguém estar se perguntando, as variáveis ​​de membro não podem ser virtuais - virtual é um especificador de funções . Referência do SO: stackoverflow.com/questions/3698831/…
rholmes
34

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 ...

lenkite
fonte
10

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.

wilhelmtell
fonte
7

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.

wilhelmtell
fonte
2
Isso não é uma questão de ambiguidades mais ou menos elegantes ou de resolução (você sempre pode usar especificações xxx :: explícitas para isso). Com herança não virtual, todas as instâncias da classe DD têm duas instâncias independentes de B. Assim que a classe possui um único membro de dados não estático, a herança virtual e não virtual diferem em mais do que apenas na sintaxe.
user3489112
@ user3489112 Assim que ... nada. A herança virtual e não virtual diferem semanticamente, ponto final.
precisa
4

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

Luc Hermitte
fonte
1

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.

Baltimark
fonte
11
Uma "classe base" usada em uma herança virtual se torna uma "classe base virtual" (no contexto dessa herança precisa).
Luc Hermitte 22/09/08
1

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).

wilhelmtell
fonte
1

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);
}
Ciro Santilli adicionou uma nova foto
fonte
2
assert(A::aDefault == 0);da função principal me dá um erro de compilação: aDefault is not a member of Ausando o gcc 5.4.0. O que é suposto fazer?
precisa saber é o seguinte
@SebTu ah obrigado, apenas algo que eu esqueci de remover da pasta de cópia, removi-a agora. O exemplo ainda deve ser significativo sem ele.
Ciro Santilli escreveu
0

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

bradtgmurray
fonte
6
Não existe "classes virtuais" em C ++. No entanto, existem "classes base virtuais" que são "virtuais" em relação a uma determinada herança. O que você se refere é o que é oficialmente chamado de "classes abstratas".
Luc Hermitte 22/09/08
@ LucHermitte, definitivamente existem classes virtuais em C ++. Verifique isto: en.wikipedia.org/wiki/Virtual_class .
Rafid
"erro: 'virtual' só pode ser especificado para funções". Não sei que língua é essa. Mas definitivamente não existe classe virtual em C ++.
Luc Hermitte
0

Herança regular

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

virtualnã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

Herança virtual

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+24para DerivedClass2::DerivedClass2()cuja montagem é idêntica, DerivedClass1::DerivedClass1()exceto para a linha mov DWORD PTR [rax+8], 3que 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 DerivedDerivedClassv 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
Lewis Kelsey
fonte