En C ++, ¿qué es una clase base virtual?

403

Quiero saber qué es una " clase base virtual " y qué significa.

Déjame mostrarte un ejemplo:

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

class Bar : public virtual Foo
{
public:
    void DoSpecific() { /* ... */ }
};
popopome
fuente
¿deberíamos usar clases base virtuales en 'herencia múltiple' porque si la clase A tiene la variable miembro int a y la clase B también tiene el miembro int a y la clase c hereda las clases A y B, cómo decidimos cuál 'a' usar?
Namit Sinha
2
@NamitSinha no, la herencia virtual no resuelve ese problema. El miembro a sería ambiguo de todos modos
Ichthyo
@NamitSinha La herencia virtual no es una herramienta mágica para eliminar múltiples ambigüedades relacionadas con la herencia. "Resuelve" un "problema" de tener una base indirecta más de una vez. Lo cual es solo un problema si se pretendía compartir (a menudo, pero no siempre, el caso).
curioso

Respuestas:

533

Las clases base virtuales, usadas en herencia virtual, son una forma de prevenir que aparezcan múltiples "instancias" de una clase dada en una jerarquía de herencia cuando se usa herencia múltiple.

Considere el siguiente escenario:

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

La jerarquía de clases anterior da como resultado el "diamante temido" que se ve así:

  A
 / \
B   C
 \ /
  D

Una instancia de D estará compuesta por B, que incluye A, y C, que también incluye A. Por lo tanto, tiene dos "instancias" (a falta de una mejor expresión) de A.

Cuando tienes este escenario, tienes la posibilidad de ambigüedad. Qué sucede cuando haces esto:

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

La herencia virtual está ahí para resolver este problema. Cuando especifica virtual cuando hereda sus clases, le está diciendo al compilador que solo desea una única instancia.

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

Esto significa que solo hay una "instancia" de A incluida en la jerarquía. Por lo tanto

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

Este es un mini resumen. Para obtener más información, lea esto y esto . Un buen ejemplo también está disponible aquí .

DO
fuente
77
@Bohdan no, no lo hace :)
OJ.
66
@OJ. ¿Por qué no? Son hilarantes :)
Bohdan
15
@Bohdan usa palabras clave virtuales tanto como menos, porque cuando usamos palabras clave virtuales, se aplica un mecanismo pesado. Entonces, la eficiencia de su programa se reducirá.
Sagar
73
Su diagrama de "diamante temido" es confuso, aunque parece ser de uso común. Esto es en realidad un diagrama que muestra las relaciones de herencia de clase, no un diseño de objeto. La parte confusa es que si usamos virtual, el diseño del objeto se parece al diamante; y si no usamos virtual, el diseño del objeto se ve como una estructura de árbol que contiene dos As
MM
55
Tengo que rechazar esta respuesta por la razón descrita por MM: el diagrama expresa lo contrario de la publicación.
David Stone el
251

Sobre el diseño de la memoria

Como nota al margen, el problema con el Diamante temido es que la clase base está presente varias veces. Entonces, con una herencia regular, crees que tienes:

  A
 / \
B   C
 \ /
  D

Pero en el diseño de la memoria, tienes:

A   A
|   |
B   C
 \ /
  D

Esto explica por qué cuando llama D::foo(), tiene un problema de ambigüedad. Pero el verdadero problema viene cuando quieres usar una variable miembro de A. Por ejemplo, digamos que tenemos:

class A
{
    public :
       foo() ;
       int m_iValue ;
} ;

Cuando intente acceder m_iValuedesde D, el compilador protestará, porque en la jerarquía verá dos m_iValue, no uno. Y si modifica uno, digamos, B::m_iValue(ese es el A::m_iValuepadre de B), C::m_iValueno será modificado (ese es el A::m_iValuepadre de C).

Aquí es donde la herencia virtual es útil, ya que con ella, volverá a un verdadero diseño de diamante, no solo con un foo()método, sino también uno y solo uno m_iValue.

¿Qué puede salir mal?

Imagina:

  • A Tiene alguna característica básica.
  • B le agrega algún tipo de genial conjunto de datos (por ejemplo)
  • Cagrega algunas características interesantes como un patrón de observación (por ejemplo, encendido m_iValue).
  • Dhereda de By C, y por lo tanto de A.

Con una herencia normal, modificar m_iValuedesde Des ambiguo y esto debe resolverse. Incluso si es así, hay dos m_iValuesdentro D, por lo que será mejor que recuerdes eso y actualices los dos al mismo tiempo.

Con la herencia virtual, modificar m_iValuedesde Destá bien ... Pero ... Digamos que sí D. A través de su Cinterfaz, adjuntaste un observador. Y a través de su Binterfaz, actualiza la matriz genial, que tiene el efecto secundario de cambiar directamente m_iValue...

Como el cambio m_iValuese realiza directamente (sin usar un método de acceso virtual), Cno se llamará al observador que "escucha" , porque el código que implementa la escucha está dentro Cy Bno lo sabe ...

Conclusión

Si tiene un diamante en su jerarquía, significa que tiene un 95% de probabilidad de haber hecho algo mal con dicha jerarquía.

paercebal
fuente
Su 'lo que podría salir mal' se debe al acceso directo a un miembro base, no a una herencia múltiple. Deshágase de 'B "y tendrá el mismo problema. La regla básica de:' si no es privado, debería ser virtual 'evita el problema. M_iValue no es virtual y por lo tanto debería ser privado
Chris Dodd
44
@ Chris Dodd: No exactamente. Lo que sucede con m_iValue le habría pasado a cualquier símbolo ( por ejemplo, typedef, miembro variable, función miembro, conversión a la clase base, etc. ). Esto realmente es un problema de herencia múltiple, un problema que los usuarios deben tener en cuenta para usar la herencia múltiple correctamente, en lugar de seguir el camino de Java y concluir "La herencia múltiple es 100% malvada, hagámoslo con las interfaces".
2010
Hola, cuando usemos una palabra clave virtual, solo habrá una copia de A. Mi pregunta es, ¿cómo sabemos si proviene de B o C? ¿Mi pregunta es válida?
user875036
@ user875036: A viene tanto de B como de C. De hecho, la virtualidad cambia algunas cosas (por ejemplo, D llamará al constructor de A, no B ni C). Tanto B como C (y D) tienen un puntero a A.
paercebal
3
FWIW, en caso de que alguien se pregunte, las variables miembro no pueden ser virtuales: virtual es un especificador de funciones . Referencia de SO: stackoverflow.com/questions/3698831/…
rholmes
34

Explicar la herencia múltiple con bases virtuales requiere un conocimiento del modelo de objetos C ++. Y explicar el tema claramente es mejor hacerlo en un artículo y no en un cuadro de comentarios.

La mejor explicación legible que encontré que resolvió todas mis dudas sobre este tema fue este artículo: http://www.phpcompiler.org/articles/virtualinheritance.html

Realmente no necesitará leer nada más sobre el tema (a menos que sea un escritor compilador) después de leer eso ...

lenkita
fuente
10

Una clase base virtual es una clase que no se puede instanciar: no se puede crear un objeto directo a partir de ella.

Creo que estás confundiendo dos cosas muy diferentes. La herencia virtual no es lo mismo que una clase abstracta. La herencia virtual modifica el comportamiento de las llamadas a funciones; a veces resuelve llamadas a funciones que de otro modo serían ambiguas, a veces difiere el manejo de llamadas a funciones a una clase distinta de la que cabría esperar en una herencia no virtual.

Wilhelmtell
fuente
7

Me gustaría añadir a las amables aclaraciones de OJ.

La herencia virtual no viene sin precio. Al igual que con todas las cosas virtuales, obtienes un éxito de rendimiento. Hay una forma de evitar este éxito de rendimiento que posiblemente sea menos elegante.

En lugar de romper el diamante derivando virtualmente, puede agregar otra capa al diamante, para obtener algo como esto:

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

Ninguna de las clases hereda virtualmente, todas heredan públicamente. Las clases D21 y D22 ocultarán la función virtual f (), que es ambigua para DD, quizás declarando la función privada. Cada uno de ellos definiría una función de contenedor, f1 () y f2 () respectivamente, cada uno de los cuales llama a la clase local (privado) f (), resolviendo así los conflictos. La clase DD llama a f1 () si quiere D11 :: f () y f2 () si quiere D12 :: f (). Si define los contenedores en línea, probablemente obtendrá aproximadamente cero sobrecarga.

Por supuesto, si puede cambiar D11 y D12, puede hacer el mismo truco dentro de estas clases, pero a menudo ese no es el caso.

Wilhelmtell
fuente
2
Esto no es una cuestión de más o menos elegante o de resolver ambigüedades (siempre puede usar especificaciones explícitas xxx :: para eso). Con la herencia no virtual, cada instancia de la clase DD tiene dos instancias independientes de B. Tan pronto como la clase tenga un solo miembro de datos no estático, la herencia virtual y no virtual difieren en algo más que la sintaxis.
user3489112
@ user3489112 Tan pronto como ... nada. La herencia virtual y no virtual difiere semánticamente, punto.
curioso
1

Estás siendo un poco confuso. No sé si estás mezclando algunos conceptos.

No tiene una clase base virtual en su OP. Solo tienes una clase base.

Hiciste herencia virtual. Esto generalmente se usa en herencia múltiple para que múltiples clases derivadas usen los miembros de la clase base sin reproducirlos.

No se crea una instancia de una clase base con una función virtual pura. Esto requiere la sintaxis a la que llega Paul. Normalmente se usa para que las clases derivadas deban definir esas funciones.

No quiero explicarte más sobre esto porque no entiendo totalmente lo que estás preguntando.

Baltimark
fuente
1
Una "clase base" que se usa en una herencia virtual se convierte en una "clase base virtual" (en el contexto de esa herencia precisa).
Luc Hermitte
1

Significa que una llamada a una función virtual se reenviará a la clase "correcta".

Preguntas frecuentes sobre C ++ Lite FTW.

En resumen, a menudo se usa en escenarios de herencia múltiple, donde se forma una jerarquía de "diamantes". La herencia virtual romperá la ambigüedad creada en la clase inferior, cuando se llama a la función en esa clase y la función debe resolverse en la clase D1 o D2 por encima de esa clase inferior. Ver el artículo de preguntas frecuentes para obtener un diagrama y detalles.

También se usa en la delegación hermana , una característica poderosa (aunque no para los débiles de corazón). Vea estas preguntas frecuentes.

Consulte también el Artículo 40 en la Eficaz tercera edición de C ++ (43 en la 2da edición).

Wilhelmtell
fuente
1

Ejemplo de uso ejecutable de herencia de diamantes

Este ejemplo muestra cómo usar una clase base virtual en el escenario típico: para resolver la herencia 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 郝海东 冠状 病 六四 事件 法轮功
fuente
2
assert(A::aDefault == 0);desde la función principal me da un error de compilación: aDefault is not a member of Ausando gcc 5.4.0. ¿Qué se supone que debe hacer?
SebNag
@SebTu ah gracias, solo algo que olvidé eliminar de copiar y pegar, lo eliminé ahora. El ejemplo aún debería ser significativo sin él.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
0

Las clases virtuales no son mismo que la herencia virtual. Clases virtuales que no puede crear instancias, la herencia virtual es algo completamente diferente.

Wikipedia lo describe mejor que yo. http://en.wikipedia.org/wiki/Virtual_inheritance

Bradtgmurray
fuente
66
No existen las "clases virtuales" en C ++. Sin embargo, hay "clases base virtuales" que son "virtuales" con respecto a una herencia dada. Lo que usted refiere es lo que oficialmente se llama "clases abstractas".
Luc Hermitte
@LucHermitte, definitivamente hay clases virtuales en C ++. Verifique esto: en.wikipedia.org/wiki/Virtual_class .
Rafid
"error: 'virtual' solo se puede especificar para funciones". No sé qué idioma es este. Pero definitivamente no existe una clase virtual en C ++.
Luc Hermitte
0

Herencia regular

Con la herencia típica de 3 niveles sin herencia no virtual de diamante, cuando crea una instancia de un nuevo objeto más derivado, se llama a nuevo y el compilador resuelve el tamaño requerido para el objeto del tipo de clase y lo pasa a nuevo.

nuevo tiene una firma:

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

Y hace un llamado a malloc , devolviendo el puntero vacío

Esto luego se pasa al constructor del objeto más derivado, que llamará inmediatamente al constructor del medio y luego el constructor del medio llamará inmediatamente al constructor base. La base luego almacena un puntero a su tabla virtual al comienzo del objeto y luego sus atributos después. Esto luego regresa al constructor del medio que almacenará su puntero de tabla virtual en la misma ubicación y luego sus atributos después de los atributos que el constructor base habría almacenado. Regresa al constructor más derivado, que almacena un puntero a su tabla virtual en la misma ubicación y luego sus atributos después de los atributos que el constructor central habría almacenado.

Debido a que el puntero de la tabla virtual se sobrescribe, el puntero de la tabla virtual termina siendo siempre una de las clases más derivadas. La virtualidad se propaga hacia la clase más derivada, por lo que si una función es virtual en la clase media, será virtual en la clase más derivada pero no en la clase base. Si convierte polimórficamente una instancia de la clase más derivada en un puntero a la clase base, el compilador no resolverá esto en una llamada indirecta a la tabla virtual y en su lugar llamará a la función directamente A::function(). Si una función es virtual para el tipo al que la ha convertido, se resolverá con una llamada a la tabla virtual, que siempre será la de la clase más derivada. Si no es virtual para ese tipo, simplemente llamará Type::function()y le pasará el puntero del objeto, se convertirá en Tipo.

En realidad, cuando digo puntero a su tabla virtual, en realidad siempre hay un desplazamiento de 16 en la tabla 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

virtualno se requiere nuevamente en clases más derivadas si es virtual en una clase menos derivada porque se propaga. Pero se puede usar para mostrar que la función es de hecho una función virtual, sin tener que verificar las clases que hereda las definiciones de tipo.

override es otro protector del compilador que dice que esta función está anulando algo y, si no es así, arroja un error del compilador.

= 0 significa que esta es una función abstracta

final evita que una función virtual se implemente nuevamente en una clase más derivada y se asegurará de que la tabla virtual de la clase más derivada contenga la función final de esa clase.

= default hace explícito en la documentación que el compilador usará la implementación predeterminada

= delete dar un error del compilador si se intenta una llamada a esto

Herencia 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();
 }

Sin heredar virtualmente la clase de bajo, obtendrá un objeto que se ve así:

En lugar de esto:

Es decir, habrá 2 objetos base.

En la situación de herencia virtual de diamantes anterior, después de llamar a new, llama al constructor más derivado y en ese constructor, llama a los 3 constructores derivados que pasan compensaciones a su tabla de tabla virtual, en lugar de llamar simplemente llamando DerivedClass1::DerivedClass1()yDerivedClass2::DerivedClass2() y luego los que llama tantoBase::Base()

Lo siguiente está todo compilado en modo de depuración -O0, por lo que habrá un ensamblaje 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

Llama Base::Base()con un puntero al desplazamiento de objeto 32. Base almacena un puntero a su tabla virtual en la dirección que recibe y sus miembros después.

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()luego llama DerivedClass1::DerivedClass1()con un puntero al objeto offset 0 y también pasa la dirección 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()luego pasa la dirección del objeto + 16 y la dirección de VTT para DerivedDerivedClass+24a DerivedClass2::DerivedClass2()cuyo montaje es idéntica a DerivedClass1::DerivedClass1()excepción de la línea mov DWORD PTR [rax+8], 3que, obviamente, tiene un 4 en vez de 3 para d = 4.

Después de esto, reemplaza los 3 punteros de la tabla virtual en el objeto con punteros para desplazar en DerivedDerivedClassvtable a la representación para esa clase.

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
fuente