2010-07-27 9 views
6

Cuantos vptrs se necesitan normalmente para un objeto cuyo clas (child) tiene herencia simple con una clase base que hereda múltiples base1 y base2. ¿Cuál es la estrategia para identificar cuántos vptrs ha proporcionado un objeto que tiene un par de herencia única y herencia múltiple? Aunque el estándar no especifica sobre vptrs, solo quiero saber cómo una implementación implementa la función virtual.¿Cuántos vptr tendrá un objeto de clase (usa herencia única/múltiple)?

Respuesta

6

¿Por qué te importa? La respuesta simple es suficiente, pero supongo que quieres algo más completo.

Esto no forma parte del estándar, por lo que cualquier implementación es libre de hacer lo que deseen, pero una regla general es que en una implementación que utiliza punteros de tabla virtual, como una aproximación aproximada, para el despacho dinámico que Necesitamos como máximo tantos punteros a tablas virtuales como clases que agregan un nuevo método virtual a la jerarquía. (En algunos casos la tabla virtual puede extenderse, y la base y tipos derivados compartir una única vptr)

// some examples: 
struct a { void foo(); };   // no need for virtual table 
struct b : a { virtual foo1(); }; // need vtable, and vptr 
struct c : b { void bar(); };  // no extra virtual table, 1 vptr (b) suffices 
struct d : b { virtual bar(); }; // extra vtable, need b.vptr and d.vptr 

struct e : d, b {};     // 3 vptr, 2 for the d subobject and one for 
            // the additional b 
struct f : virtual b {}; 
struct g : virtual b {}; 
struct h : f, g {};     // single vptr, only b needs vtable and 
            // there is a single b 

Básicamente cada subobjeto de un tipo que requiere su propio envío dinámico (no se puede reutilizar directamente los padres) necesitaría su propia tabla virtual y vptr.

En realidad, los compiladores combinan diferentes tablas en un solo vtable. Cuando d agrega una nueva función virtual sobre el conjunto de funciones en b, el compilador combinará las dos tablas potenciales en una sola al agregar las nuevas ranuras al final de la tabla de visitas, por lo que la tabla de datos para d será una versión extendida de el vtable para b con elementos adicionales al final manteniendo compatibilidad binaria (es decir, el d vtable puede interpretarse como un b vtable para acceder a los métodos disponibles en b), y el objeto d tendrá un solo vptr.

En el caso de la herencia múltiple, las cosas se vuelven un poco más complicadas ya que cada base necesita tener la misma disposición que un subobjeto del objeto completo que si fuera un objeto separado, por lo que habrá vptrs adicionales apuntando a diferentes regiones en el vtable completo del objeto.

Finalmente, en el caso de la herencia virtual, las cosas se vuelven aún más complicadas, y puede haber múltiples tablas para el mismo objeto completo con el vptr actualizado a medida que la construcción/destrucción evoluciona (las vptr siempre se actualizan a medida que la construcción/destrucción evoluciona, pero sin herencia virtual VPTR apuntará a vtables de la base, mientras que en el caso de la herencia virtual habrá múltiples vtables para el mismo tipo)

+1

"' struct d: b {barra virtual();}; // vtable extra, necesita b.vptr y d.vptr' "No creo que haya ningún compilador que presente más de un vptr en una clase con SI no virtual. – curiousguy

4

la letra pequeña

cualquier cosa con respecto VPTR/no se especifica vtable, por lo esto va a depender del compilador para los detalles finos, pero los casos simples se manejan igual en casi todas las versiones modernas. iler (escribo "casi" por si acaso).

Has sido advertido.

diseño del objeto: la herencia no virtual

Si heredar de clases base, y tienen un VPTR, que, naturalmente, tiene el mayor número heredaron VPTR en su clase.

La pregunta es: ¿Cuándo el compilador agregará un vptr a una clase que ya tiene un vptr heredado?

El compilador tratará de evitar la adición de VPTR redundante:

struct B { 
    virtual ~B(); 
}; 

struct D : B { 
    virtual void foo(); 
}; 

Aquí B tiene un VPTR, por lo D no obtiene su propia VPTR, se reutiliza el VPTR existente; el vtable de B se extiende con una entrada para foo(). La viable para D se "deriva" de la viable para B, pseudo-código:

struct B_vtable { 
    typeinfo *info; // for typeid, dynamic_cast 
    void (*destructor)(B*); 
}; 

struct D_vtable : B_vtable { 
    void (*foo)(D*); 
}; 

la letra pequeña, otra vez: esto es una simplificación de un vtable real, para hacerse una idea.

herencia Virtual

para la herencia única no virtual, casi no hay margen para la variación entre implementaciones. Para la herencia virtual, hay muchas más variaciones entre los compiladores.

struct B2 : virtual A { 
}; 

Hay una conversión B2*-A*, por lo que un objeto B2 debe proporcionar esta funcionalidad:

  • ya sea con un miembro de A*
  • ya sea con un miembro int: offset_of_A_from_B2
  • cualquiera usando su vptr, almacenando offset_of_A_from_B2 en el vtable

En general, una clase no reutilizará el vptr de su clase base virtual (pero puede hacerlo en un caso muy especial).

Cuestiones relacionadas