¿Cómo sabe el compilador de C ++ a qué implementación de una función virtual llamar?

StackOverflow https://stackoverflow.com/questions/203126

  •  03-07-2019
  •  | 
  •  

Pregunta

Aquí hay un ejemplo de polimorfismo de http://www.cplusplus.com/doc /tutorial/polymorphism.html (editado para facilitar la lectura):

// abstract base class
#include <iostream>
using namespace std;

class Polygon {
    protected:
        int width;
        int height;
    public:
        void set_values(int a, int b) { width = a; height = b; }
        virtual int area(void) =0;
};

class Rectangle: public Polygon {
    public:
        int area(void) { return width * height; }
};

class Triangle: public Polygon {
    public:
        int area(void) { return width * height / 2; }
};

int main () {
    Rectangle rect;
    Triangle trgl;
    Polygon * ppoly1 = &rect;
    Polygon * ppoly2 = &trgl;
    ppoly1->set_values (4,5);
    ppoly2->set_values (4,5);
    cout << ppoly1->area() << endl; // outputs 20
    cout << ppoly2->area() << endl; // outputs 10
    return 0;
}

Mi pregunta es ¿cómo sabe el compilador que ppoly1 es un rectángulo y que ppoly2 es un triángulo, para que pueda llamar a la función area () correcta? Podría descubrirlo mirando el " Polygon * ppoly1 = & # 9645; " línea y sabiendo que rect es un Rectángulo, pero eso no funcionaría en todos los casos, ¿verdad? ¿Qué pasaría si hicieras algo como esto?

cout << ((Polygon *)0x12345678)->area() << endl;

Suponiendo que tienes permiso para acceder a esa área aleatoria de la memoria.

Lo probaría, pero no puedo con la computadora que tengo encendida en este momento.

(Espero que no me esté perdiendo algo obvio ...)

¿Fue útil?

Solución

Cada objeto (que pertenece a una clase con al menos una función virtual) tiene un puntero, llamado vptr . Apunta al vtbl de su clase real (que cada clase con funciones virtuales tiene al menos una; posiblemente más de una para algunos escenarios de herencia múltiple).

El vtbl contiene un montón de punteros, uno para cada función virtual. Entonces, en el tiempo de ejecución, el código solo usa el vptr del objeto para ubicar el vtbl , y desde allí la dirección de la función sobrescrita real.

En su caso específico, Polygon , Rectangle y Triangle cada uno tiene un vtbl , cada uno con uno Entrada que apunta a su método area relevante. Su ppoly1 tendrá un vptr que apunta al Rectangle del vtbl y ppoly2 de manera similar con Triangle ' vtbl . Espero que esto ayude!

Otros consejos

Chris Jester-Young da la respuesta básica a esta pregunta.

Wikipedia tiene un tratamiento más profundo.

Si desea conocer todos los detalles sobre cómo funciona este tipo de cosas (y para todo tipo de herencia, incluida la herencia múltiple y virtual), uno de los mejores recursos es Stan Lippman's " Dentro del Modelo de objetos C ++ " ;.

Sin tener en cuenta los aspectos del enlace, en realidad no es el compilador lo que determina esto.

Es el tiempo de ejecución de C ++ que evalúa, a través de vtables y vpointers, qué es realmente el objeto derivado en tiempo de ejecución.

Recomiendo altamente el libro Effective C ++ de Scott Meyer por sus buenas descripciones sobre cómo se hace esto.

¡Incluso cubre cómo se ignoran los parámetros predeterminados en un método en una clase derivada y aún se toman todos los parámetros predeterminados en una clase base! Eso es vinculante.

Para responder a la segunda parte de tu pregunta: esa dirección probablemente no tendrá una tabla en V en el lugar correcto, y la locura seguirá. Además, no está definido de acuerdo con el estándar.

cout << ((Polygon *)0x12345678)->area() << endl;

Este código es un desastre esperando a suceder. El compilador lo compilará bien, pero cuando se trata de tiempo de ejecución, no apuntará a una tabla v válida y, si tiene suerte, el programa simplemente se bloqueará.

En C ++, no deberías usar moldes antiguos de estilo C como este, deberías usar dynamic_cast así:

Polygon *obj = dynamic_cast<Polygon *>(0x12345678)->area();
ASSERT(obj != NULL);

cout << obj->area() << endl;

dynamic_cast devolverá NULL si el puntero dado no es un objeto Polygon válido, por lo que quedará atrapado por ASSERT.

Tablas de funciones virtuales. Además, los dos objetos derivados de Polygon tienen una tabla de funciones virtuales que contiene punteros a las implementaciones de todas sus funciones (no estáticas); y cuando se crea una instancia de un Triángulo, el puntero de función virtual para la función area () apunta a la función Triángulo :: area (); al crear una instancia de un rectángulo, la función area () apunta a la función rectángulo :: area (). Debido a que los punteros de función virtual se almacenan junto con los datos de un objeto en la memoria, cada vez que haga referencia a ese objeto como un polígono, se usará el área apropiada () para ese objeto.

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