2008-10-14 17 views
9

Aquí es un ejemplo de polimorfismo de http://www.cplusplus.com/doc/tutorial/polymorphism.html (editado para facilitar la lectura):¿Cómo sabe el compilador de C++ qué implementación de una función virtual llamar?

// 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, de modo que pueda llamar a la correcta función area()? Podría averiguarlo mirando la línea "Polygon * ppoly1 = ▭" y sabiendo que rect es un Rectángulo, pero eso no funcionaría en todos los casos, ¿verdad? ¿Qué pasa si hiciste algo como esto?

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

Suponiendo que tiene permiso para acceder a ese área aleatoria de memoria.

Lo probaría pero no puedo en la computadora en la que estoy en este momento.

(espero que no me falta algo obvio ...)

+3

Offtopic: ¿Por qué no votar por las otras personas que dedicaron tiempo a escribir respuestas útiles para usted? –

Respuesta

24

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 uno, posiblemente más de uno para algunos escenarios de herencia múltiple). El vtbl contiene un grupo de punteros, uno para cada función virtual Por lo tanto, en tiempo de ejecución, el código solo usa el objeto vptr para localizar el vtbl, y desde allí la dirección de la función real anulada.

En su caso específico, Polygon, Rectangle, y cada uno tiene un Trianglevtbl, cada uno con una entrada que señala a su método relevante area. Su ppoly1 tendrá un vptr apuntando a Rectangle 's vtbl, y ppoly2 de manera similar con Triangle' s vtbl. ¡Espero que esto ayude!

+0

vptr/vtbl. Rally No recuerdo aquellos en el estándar :-) Puntero a un vtable. Donde un vtable es una estructura definida por el compilador es más descriptivo. –

+0

@Martin: vptr/vtbl son los términos utilizados en el libro de Bjarne Stroustrup, The C++ Programming Language. :-) –

+0

Supongo que el estándar no requiere un vtable, simplemente sucede que la mayoría de los compiladores implementan polimorfismo utilizando uno, por lo que se ha convertido en un comportamiento más o menos estándar –

3

aspectos de la unión Sin tener en cuenta, no es en realidad el compilador que determina esto.

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

Recomiendo encarecidamente el libro de Scott Meyer Effective C++ para obtener 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 los parámetros predeterminados en una clase base! Eso es vinculante.

1

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

5

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

Wikipedia tiene un tratamiento más en profundidad.

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 "Inside the C++ Object Model" de Stan Lippman.

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

Este código es un desastre que está por ocurrir. El compilador compilará todo correctamente, pero cuando se trata de tiempo de ejecución, no estará apuntando a una tabla v válida y si tiene suerte, el programa se bloqueará.

En C++, no se debe usar al viejo estilo C lanza como este, se debe utilizar 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álida por lo tanto, será atrapado por ASSERT.

+0

¡No puede dinámicamente_cast desde un número entero! (De hecho, tampoco puedes dynamic_cast desde el vacío *, debes comenzar desde un puntero/referencia de un tipo que tenga alguna relación con el tipo al que estás enviando). –

+0

Así que mi punto es que con una dirección aleatoria como reinterpret_cast (0x12345678) Estás en una zona de comportamiento indefinido sin importar qué. :-P –

+0

De hecho, he hecho este tipo de cosas para almacenar punteros de objeto en un cuadro de lista de Windows. Es cierto que tengo que escribirlo así: MYTYPE * obj = dynamic_cast ((MYTYPE *) listbox.GetItemData (item)); El dynamic_cast es un poco más seguro que un elenco directo. –

1

Tablas de funciones virtuales. A saber, ambos objetos derivados de Polygon tienen una tabla de funciones virtuales que contiene indicadores de función para las implementaciones de todas sus funciones (no estáticas); y cuando instancia un Triangle, el puntero de función virtual para la función area() apunta a la función Triangle :: area(); cuando instancia un Rectángulo, la función de área() apunta a la función Rectángulo :: área(). Debido a que los punteros de funciones virtuales 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.

Cuestiones relacionadas