c++ vtable vptr

c++ - Detalles de implementación de despacho virtual



vtable vptr (3)

En primer lugar, quiero aclarar que entiendo que no hay noción de vtables y vptrs en el estándar de C ++ . Sin embargo, creo que prácticamente todas las implementaciones implementan el mecanismo de envío virtual de la misma manera (corríjame si me equivoco, pero esta no es la pregunta principal). Además, creo que sé cómo funcionan las funciones virtuales , es decir, siempre puedo decir a qué función se llamará, solo necesito los detalles de la implementación.

Supongamos que alguien me pregunta lo siguiente:
"Tiene una clase base B con funciones virtuales v1, v2, v3 y clase derivada D: B que anula las funciones v1 y v3 y agrega una función virtual v4. Explique cómo funciona el despacho virtual".

Yo respondería así:
Para cada clase con funciones virtuales (en este caso B y D) tenemos una matriz separada de punteros a funciones llamada vtable.
El vtable para B contendría

&B::v1 &B::v2 &B::v3

El vtable para D contendría

&D::v1 &B::v2 &D::v3 &D::v4

Ahora la clase B contiene un puntero miembro vptr. D naturalmente lo hereda y por lo tanto también lo contiene. En el constructor y destructor de BB, vptr apunta a vtable de B. En el constructor y destructor de DD, se establece que apunte a la vtable de D.
Cualquier llamada a una función virtual f en un objeto x de la clase polimórfica X se interpreta como una llamada a x.vptr [posición de f en vtables]

Las preguntas son:
1. ¿Tengo algún error en la descripción anterior?
2. ¿Cómo sabe el compilador la posición de f en vtable (en detalle, por favor)
3. ¿Significa esto que si una clase tiene dos bases, entonces tiene dos vptrs? ¿Qué está pasando en este caso? (trate de describir de la misma manera que lo hice, con el mayor detalle posible)
4. ¿Qué está pasando en una jerarquía de diamantes con A en la parte superior B, C en el medio y D en la parte inferior? (A es una clase base virtual de B y C)

Gracias por adelantado.


  1. Me parece bien
  2. Implementación específica, pero la mayoría solo están en el orden del código fuente, es decir, en el orden en que aparecen en la clase, comenzando con la clase base y luego agregando nuevas funciones virtuales de las derivadas. Mientras el compilador tenga una forma determinista de hacer esto, entonces cualquier cosa que quiera hacer está bien. Sin embargo, en Windows, para crear tablas V compatibles con COM, tiene que estar en el orden de origen

  3. (no es seguro)

  4. (supongo) Un diamante solo significa que podría tener dos copias de una clase base B. La herencia virtual las combinará en una sola instancia. Entonces, si configura un miembro a través de D1, puede leerlo a través de D2. (con C derivada de D1, D2, cada una derivada de B). Creo que en ambos casos, los vtables serían idénticos, ya que los punteros de función son los mismos: la memoria para los miembros de datos es lo que se fusiona.

Comentarios:

  • ¡No creo que los destructores entren en esto!

  • Una llamada como, por ejemplo, D d; d.v1(); D d; d.v1(); probablemente no se implementará a través de vtable, ya que el compilador puede resolver la dirección de la función en tiempo de compilación / enlace.

  • El compilador conoce la posición de f porque lo puso ahí!

  • Sí, una clase con varias clases base normalmente tendrá múltiples vptrs (asumiendo funciones virtuales en cada clase base).

  • Los libros de Scott Meyers "Effective C ++" explican la herencia múltiple y los diamantes mejor que yo; Recomiendo leerlos por esta (y muchas otras) razones. ¡Considéralas lectura esencial!


1. ¿Tengo algún error en la descripción anterior?

Todo bien. :-)

2. ¿Cómo sabe el compilador la posición de f en vtable?

Cada proveedor tendrá su propia forma de hacer esto, pero siempre pienso en el vtable como un mapa de la firma de la función miembro con la compensación de memoria. Así que el compilador solo mantiene esta lista.

3. ¿Significa esto que si una clase tiene dos bases, entonces tiene dos vptrs? ¿Qué está pasando en este caso?

Normalmente, los compiladores componen una nueva vtable que consta de todas las vtables de las bases virtuales unidas en el orden en que se especificaron, junto con el puntero vtable de la base virtual. Siguen esto con las funciones vtable de la clase derivada. Esto es extremadamente específico del proveedor, pero para la class D : B1, B2 , normalmente se ve D._vptr[0] == B1._vptr .

Esa imagen es en realidad para componer los campos miembros de un objeto, pero el compilador puede componer vtables de la misma manera (en la medida en que lo entiendo).

4. ¿Qué está pasando en una jerarquía de diamantes con A en la parte superior B, C en el medio y D en la parte inferior? (A es una clase base virtual de B y C)

La respuesta corta? Infierno absoluto ¿Heredaste virtualmente las dos bases? ¿Solo uno de ellos? ¿Ninguno de ellos? En última instancia, se utilizan las mismas técnicas de composición de una vtable para la clase, pero la forma en que se hace varía enormemente, ya que la forma en que debe hacerse no está en absoluto establecida en piedra. Aquí hay una explicación decente para resolver el problema de la jerarquía de los diamantes, pero, como la mayoría de esto, es bastante específica del proveedor.