¿Cuándo se crea un vtable en C ++?

¿Cuándo exactamente el comstackdor crea una tabla de funciones virtuales?

1) cuando la clase contiene al menos una función virtual.

O

2) cuando la clase base inmediata contiene al menos una función virtual.

O

3) cuando cualquier clase padre en cualquier nivel de la jerarquía contiene al menos una función virtual.

Una pregunta relacionada con esto: ¿es posible renunciar al envío dynamic en una jerarquía de C ++?

por ejemplo, considere el siguiente ejemplo.

#include  using namespace std; class A { public: virtual void f(); }; class B: public A { public: void f(); }; class C: public B { public: void f(); }; 

¿Qué clases contendrán una V-Table?

Como B no declara f () como virtual, ¿la clase C obtiene un polymorphism dynamic?

Más allá de que “los vtables son específicos de la implementación” (que lo son), si se usa un vtable: habrá tablas virtuales únicas para cada una de sus clases. Aunque B :: f y C :: f no se declaran virtuales, porque hay una firma coincidente en un método virtual de una clase base ( A en su código), B :: f y C :: f son implícitamente virtuales . Debido a que cada clase tiene al menos un método virtual único ( B :: f anula A :: f para instancias B y C :: f de manera similar para instancias C ), necesita tres tablas virtuales.

Por lo general, no debe preocuparse por tales detalles. Lo que importa es si tiene despacho virtual o no. No tiene que utilizar el despacho virtual, al especificar explícitamente a qué función llamar, pero esto generalmente solo es útil cuando se implementa un método virtual (como llamar al método de la base). Ejemplo:

 struct B { virtual void f() {} virtual void g() {} }; struct D : B { virtual void f() { // would be implicitly virtual even if not declared virtual B::f(); // do D-specific stuff } virtual void g() {} }; int main() { { B b; bg(); bB::g(); // both call B::g } { D d; B& b = d; bg(); // calls D::g bB::g(); // calls B::g bD::g(); // not allowed dD::g(); // calls D::g void (B::*p)() = &B::g; (b.*p)(); // calls D::g // calls through a function pointer always use virtual dispatch // (if the pointed-to function is virtual) } return 0; } 

Algunas reglas concretas que pueden ayudar; pero no me cites sobre esto, probablemente me haya perdido algunos casos extremos:

  • Si una clase tiene métodos virtuales o bases virtuales, incluso si se hereda, las instancias deben tener un puntero vtable.
  • Si una clase declara métodos virtuales no heredados (como cuando no tiene una clase base), entonces debe tener su propia tabla virtual.
  • Si una clase tiene un conjunto diferente de métodos de anulación que su primera clase base, entonces debe tener su propia tabla de navegación y no puede reutilizar la base. (Los destructores comúnmente requieren esto).
  • Si una clase tiene múltiples clases base, con la segunda base o base posterior con métodos virtuales:
    • Si ninguna base anterior tiene métodos virtuales y la Optimización de Base Vacía se aplicó a todas las bases anteriores, entonces trate esta base como la primera clase base.
    • De lo contrario, la clase debe tener su propio vtable.
  • Si una clase tiene clases base virtuales, debe tener su propia tabla virtual.

Recuerde que un vtable es similar a un miembro de datos estáticos de una clase, y las instancias solo tienen punteros a estos.

También vea el artículo completo C ++: Under the Hood (Marzo de 1994) de Jan Gray. ( Pruebe Google si ese enlace muere)

Ejemplo de reutilización de un vtable:

 struct B { virtual void f(); }; struct D : B { // does not override B::f // does not have other virtuals of its own void g(); // still might have its own non-virtuals int n; // and data members }; 

En particular, note que el dtor de B no es virtual (y es probable que sea un error en el código real ), pero en este ejemplo, las instancias D apuntarán a las mismas instancias de vtable como B.

La respuesta es, depende’. Depende de lo que quiere decir con ‘contener un vbl’ y depende de las decisiones tomadas por el implementador del comstackdor particular.

Estrictamente hablando, ninguna ‘clase’ contiene una tabla de funciones virtuales. Algunas instancias de algunas clases contienen punteros a tablas de funciones virtuales. Sin embargo, esa es solo una posible implementación de la semántica.

En el extremo, un comstackdor hipotéticamente podría poner un número único en la instancia que se indiza en una estructura de datos utilizada para seleccionar la instancia de función virtual apropiada.

Si preguntas, ‘¿Qué hace GCC?’ o ‘¿Qué hace Visual C ++?’ entonces podrías obtener una respuesta concreta.

La respuesta de @Sassan Syed probablemente esté más cerca de lo que preguntaba, pero es muy importante mantener los conceptos aquí.

Hay un comportamiento (despacho dynamic basado en qué clase se actualizó) y hay implementación . Su pregunta utilizó la terminología de implementación, aunque sospecho que estaba buscando una respuesta de comportamiento.

La respuesta de comportamiento es la siguiente: cualquier clase que declare o herede una función virtual mostrará un comportamiento dynamic en las llamadas a esa función. Cualquier clase que no, no lo hará.

En lo que respecta a la implementación, el comstackdor puede hacer lo que quiera para lograr ese resultado.

Responder

un vtable se crea cuando una statement de clase contiene una función virtual. Un vtable se presenta cuando un padre, en cualquier lugar de la jerarquía, tiene una función virtual, llamemos a este padre Y. Cualquier padre de Y NO tendrá un vtable (a menos que tenga un virtual para alguna otra función en su jerarquía).

Siga leyendo para discutir y probar

– explicación –

Cuando especifica una función de miembro como virtual, existe la posibilidad de que intente usar subclases a través de una clase base polimórficamente en tiempo de ejecución. Para mantener la garantía de rendimiento de C ++ sobre el diseño del lenguaje, ofrecieron la estrategia de implementación más ligera posible, es decir, un nivel de indirección, y solo cuando una clase podría usarse polimórficamente en tiempo de ejecución, y el progtwigdor especifica esto al configurar al menos una función para que sea virtual.

No incurre en el costo del vtable si evita la palabra clave virtual.

– editar: para reflejar su edición –

Solo cuando una clase base contiene una función virtual, cualquier otra subclase contiene un vtable. Los padres de dicha clase base no tienen un vtable.

En su ejemplo, las tres clases tendrán un vtable, esto es porque puede intentar usar las tres clases a través de un A*.

–test – GCC 4+ –

 #include  class test_base { public: void x(){std::cout << "test_base" << "\n"; }; }; class test_sub : public test_base { public: virtual void x(){std::cout << "test_sub" << "\n"; } ; }; class test_subby : public test_sub { public: void x() { std::cout << "test_subby" << "\n"; } }; int main() { test_sub sub; test_base base; test_subby subby; test_sub * psub; test_base *pbase; test_subby * psubby; pbase = ⊂ pbase->x(); psub = &subby; psub->x(); return 0; } 

salida

 test_base test_subby 

test_base no tiene una tabla virtual, por lo tanto, cualquier elemento que se le test_base usará x() de test_base . test_sub por otro lado cambia la naturaleza de x() y su puntero será indirecto a través de un vtable, y esto se muestra mediante el test_subby ‘s x() está ejecutando.

Por lo tanto, un vtable solo se introduce en la jerarquía cuando se usa la palabra clave virtual. Los antepasados ​​más antiguos no tienen un vtable, y si se produce un downcast estará conectado a las funciones de los antepasados.

Hizo un esfuerzo para que su pregunta fuera muy clara y precisa, pero aún faltaba un poco de información. Probablemente sepa que en las implementaciones que usan V-Table, la tabla en sí misma es normalmente una estructura de datos independiente, almacenada fuera de los objetos polimórficos, mientras que los objetos en sí mismos solo almacenan un puntero implícito a la tabla. Entonces, ¿qué es lo que estás preguntando? Podría ser:

  • ¿Cuándo un objeto obtiene un puntero implícito a V-Table insertado en él?

o

  • ¿Cuándo se crea una V-Table individual dedicada para un tipo dado en la jerarquía?

La respuesta a la primera pregunta es: un objeto obtiene un puntero implícito a V-Table insertado en él cuando el objeto es de tipo de clase polimórfica. El tipo de clase es polimórfico si contiene al menos una función virtual, o cualquiera de sus padres directos o indirectos son polimórficos (esta es la respuesta 3 de su conjunto). Tenga en cuenta también que, en el caso de una herencia múltiple, un objeto podría (y lo hará) terminar conteniendo múltiples punteros de V-Table incrustados en él.

La respuesta a la segunda pregunta podría ser la misma que la primera (opción 3), con una posible excepción. Si alguna clase polimórfica en la jerarquía de herencia única no tiene funciones virtuales propias (no hay nuevas funciones virtuales, no reemplaza la función virtual primaria), es posible que la implementación decida no crear una tabla V individual para esta clase, sino que use su V-Table para padres inmediata para esta clase también (ya que de todos modos va a ser igual). Es decir, en este caso, tanto los objetos de tipo principal como los de tipo derivado almacenarán el mismo valor en sus punteros V-Table incrustados. Esto es, por supuesto, altamente dependiente de la implementación. Revisé GCC y MS VS 2005 y no actúan de esa manera. Ambos crean una V-Table individual para la clase derivada en esta situación, pero parece recordar haber escuchado sobre implementaciones que no lo hacen.

Los estándares de C ++ no obligan a utilizar V-Tables para crear la ilusión de clases polimórficas. La mayoría de las implementaciones de tiempo usan V-Tables para almacenar la información adicional necesaria. En resumen, estas piezas de información adicionales están equipadas cuando tiene al menos una función virtual.

El comportamiento se define en el capítulo 10.3, párrafo 2 de la especificación del lenguaje C ++:

Si se declara una función de miembro virtual vf en una clase Base y en una clase Derivada, derivada directa o indirectamente de Base, se declara una función miembro vf con el mismo nombre y la misma lista de parámetros que Base :: vf, luego Derived :: vf también es virtual ( esté o no declarado ) y anula Base :: vf.

En cursiva la frase relevante. Por lo tanto, si su comstackdor crea v-tables en el sentido habitual, todas las clases tendrán una tabla v ya que todos sus métodos f () son virtuales.