Pregunta

Estoy un poco confundido acerca de vptr y la representación de objetos en la memoria, y espero que puedan ayudarme a comprender mejor el asunto.

  1. Considerar B hereda de A y ambos definen funciones virtuales f().Por lo que aprendí, la representación de un objeto de clase B en la memoria se ve así:[ vptr | A | B ]y el vtbl eso vptr apunta a contiene B::f().También entendí que lanzar el objeto desde B a A no hace nada excepto ignorar el B parte al final del objeto.¿Es verdad?¿No es incorrecto este comportamiento?Queremos ese objeto de tipo. A ejecutar A::f() método y no B::f().

  2. ¿Hay una serie de vtables en el sistema como el número de clases?

  3. ¿Cómo será un vtable ¿Cómo se ve la clase que hereda de dos o más clases?¿Cómo se representará el objeto de C en la memoria?

  4. Igual que la pregunta 3 pero con herencia virtual.

¿Fue útil?

Solución

Lo siguiente es cierto para GCC (y parece cierto para LLVM enlace), pero también puede ser cierto para el compilador que estás utilizando.Todo esto depende de la implementación y no se rige por el estándar C++.Sin embargo, GCC escribe su propio documento estándar binario, Itanio ABI.

Intenté explicar los conceptos básicos de cómo se distribuyen las mesas virtuales con palabras más simples como parte de mi artículo sobre el rendimiento de funciones virtuales en C++, que puede resultarle útil.Aquí hay respuestas a sus preguntas:

  1. Una forma más correcta de representar la representación interna del objeto es:

    | vptr | ======= | ======= |  <-- your object
           |----A----|         |
           |---------B---------|
    

    B contiene su clase base A, solo agrega un par de sus propios miembros después de su final.

    fundición de B* a A* de hecho no hace nada, devuelve el mismo puntero y vptr sigue siendo el mismo.Pero, en pocas palabras, las funciones virtuales no siempre se llaman a través de vtable.A veces se llaman igual que las otras funciones.

    Aquí hay una explicación más detallada.Debes distinguir dos formas de llamar a la función miembro:

    A a, *aptr;
    a.func();         // the call to A::func() is precompiled!
    aptr->A::func();  // ditto
    aptr->func();     // calls virtual function through vtable.
                      // It may be a call to A::func() or B::func().
    

    La cosa es que se sabe en tiempo de compilación cómo se llamará la función:a través de vtable o simplemente será una llamada habitual.Y la cosa es que el tipo de expresión de conversión se conoce en el momento de la compilación, y, por lo tanto, el compilador elige la función correcta en el momento de la compilación.

    B b, *bptr;          
    static_cast<A>(b)::func(); //calls A::func, because the type
       // of static_cast<A>(b) is A!
    

    ¡En este caso ni siquiera mira dentro de vtable!

  2. Generalmente no.Una clase puede tener varias vtables si hereda de varias bases, teniendo cada una su propia vtable.Tal conjunto de mesas virtuales forma un "grupo de mesas virtuales" (ver pt.3).

    La clase también necesita un conjunto de vtables de construcción para distribuir correctamente funciones virtuales al construir bases de un objeto complejo.Puedes leer más en el estándar que vinculé.

  3. He aquí un ejemplo.Asumir C hereda de A y B, cada clase define virtual void func(), así como a,b o c función virtual relevante a su nombre.

    El C tendrá un grupo vtable de dos vtables.Compartirá una vtable con A (la vtable donde van las funciones propias de la clase actual se llama "primaria"), y una vtable para B se adjuntará:

    | C::func()   |   a()  |  c()  || C::func()  |   b()   |
    |---- vtable for A ----|        |---- vtable for B ----| 
    |--- "primary virtual table" --||- "secondary vtable" -|
    |-------------- virtual table group for C -------------|
    

    La representación del objeto en la memoria se verá casi de la misma manera que su vtable.Sólo agrega un vptr antes de cada vtable en un grupo, y tendrá una estimación aproximada de cómo se distribuyen los datos dentro del objeto.Puedes leer sobre esto en el sección relevante del estándar binario del CCG.

  4. Las bases virtuales (algunas de ellas) se encuentran al final del grupo vtable.Esto se hace porque cada clase debe tener solo una base virtual, y si se mezclaran con vtables "habituales", entonces el compilador no podría reutilizar partes de vtables construidas para crear aquellas de clases derivadas.Esto conduciría a calcular compensaciones innecesarias y reduciría el rendimiento.

    Debido a esta ubicación, las bases virtuales también introducen elementos adicionales en sus vtables: vcall desplazamiento (para obtener la dirección de una anulación final al saltar desde el puntero a una base virtual dentro de un objeto completo al comienzo de la clase que anula la función virtual) para cada función virtual definida allí.Además cada base virtual suma vbase compensaciones, que se insertan en vtable de la clase derivada;permiten encontrar dónde comienzan los datos de la base virtual (no se puede precompilar ya que la dirección real depende de la jerarquía:Las bases virtuales están al final del objeto y el cambio desde el principio varía dependiendo de cuántas clases no virtuales herede la clase actual).

Vaya, espero no haber introducido mucha complejidad innecesaria.En cualquier caso, podrá consultar el estándar original, o cualquier documento de su propio compilador.

Otros consejos

  1. Eso me parece correcto.No está mal que si estuviera usando un puntero A, solo necesita lo que A proporciona más quizás las implementaciones de funciones B que están disponibles en A vtable (puede haber varias vtable, dependiendo del compilador y la complejidad de la jerarquía).
  2. Yo diría que sí, pero depende de la implementación del compilador, por lo que realmente no es necesario que lo sepas.
  3. y 4.Lea más.

recomendaría leer La herencia múltiple se considera útil , es un artículo largo pero aclara las cosas sobre el tema ya que explica con gran detalle cómo funciona la herencia en C++ (los enlaces de las figuras no funcionan pero están disponibles en la parte inferior de la página).

  1. Si el objeto B hereda de A, entonces la representación en memoria de B será la siguiente:

    • puntero a la tabla virtual de A
    • Unas variables/funciones específicas
    • puntero a la tabla virtual de B
    • B variables/funciones/anulaciones específicas

    Si tienes B* b = new B();(A)b->f() entonces:

    • si f fue declarada como una función virtual, entonces se llama a la implementación de B porque b es de tipo B
    • Si f no se declaró como una función virtual, cuando se llame no se buscará en la vtable la implementación correcta y se llamará a la implementación de A.
  2. Cada objeto tendrá su propia tabla virtual (no des esto por sentado, ya que tengo que investigarlo).

  3. Echa un vistazo a este para ver un ejemplo de vtable layour cuando se trata de herencia múltiple

  4. Ver este para una discusión sobre la herencia de diamantes y la representación vtable

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top