Come fa il compilatore C ++ a sapere quale implementazione di una funzione virtuale chiamare?

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

  •  03-07-2019
  •  | 
  •  

Domanda

Ecco un esempio di polimorfismo da http://www.cplusplus.com/doc /tutorial/polymorphism.html (modificato per leggibilità):

// 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;
}

La mia domanda è come fa il compilatore a sapere che ppoly1 è un rettangolo e che ppoly2 è un triangolo, in modo che possa chiamare la funzione area () corretta? Potresti scoprirlo guardando il " Polygon * ppoly1 = ? " linea e sapendo che rettangolo è un rettangolo, ma che non funzionerebbe in tutti i casi, vero? E se facessi qualcosa del genere?

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

Supponendo che ti sia permesso accedere a quell'area casuale di memoria.

Lo proverei ma non riesco sul computer in cui mi trovo al momento.

(Spero non mi manchi qualcosa di ovvio ...)

È stato utile?

Soluzione

Ogni oggetto (che appartiene a una classe con almeno una funzione virtuale) ha un puntatore, chiamato vptr . Indica il vtbl della sua classe effettiva (che ogni classe con funzioni virtuali ha almeno una delle; forse più di una per alcuni scenari con ereditarietà multipla).

Il vtbl contiene un sacco di puntatori, uno per ogni funzione virtuale. Quindi, in fase di esecuzione, il codice utilizza solo vptr per individuare vtbl , e da lì l'indirizzo dell'effettiva funzione sovrascritta.

Nel tuo caso specifico, Polygon , Rectangle e Triangle hanno ciascuno un vtbl , ciascuno con uno voce che punta al relativo metodo area . Il tuo ppoly1 avrà un vptr che punta al Rectangle del vtbl e del ppoly2 allo stesso modo con Triangle di vtbl . Spero che questo aiuti!

Altri suggerimenti

Chris Jester-Young fornisce la risposta di base a questa domanda.

Wikipedia ha un trattamento più approfondito.

Se vuoi conoscere i dettagli completi su come funziona questo tipo di cose (e per tutti i tipi di eredità, incluse eredità multiple e virtuali), una delle migliori risorse è Stan Lippman " All'interno del modello a oggetti C ++ " ;.

Ignorando gli aspetti dell'associazione, in realtà non è il compilatore a determinarlo.

È il runtime C ++ che valuta, tramite vtables e vpointer, ciò che l'oggetto derivato è effettivamente in fase di runtime.

Consiglio vivamente il libro efficace di Scott Meyer C ++ efficace per buone descrizioni su come ciò viene fatto.

Descrive anche come vengono ignorati i parametri predefiniti in un metodo in una classe derivata e vengono ancora presi tutti i parametri predefiniti in una classe base! È vincolante.

Per rispondere alla seconda parte della tua domanda: quell'indirizzo probabilmente non avrà una v-table nel posto giusto e ne deriverà la follia. Inoltre, non è definito secondo lo standard.

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

Questo codice è un disastro in attesa di accadere. Il compilatore compilerà tutto bene ma quando si tratta di runtime, non indicherai una v-table valida e se sei fortunato il programma andrà in crash.

In C ++, non dovresti usare i vecchi cast in stile C come questo, dovresti usare dynamic_cast in questo modo:

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

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

dynamic_cast restituirà NULL se il puntatore specificato non è un oggetto Polygon valido, quindi verrà bloccato da ASSERT.

Tabelle delle funzioni virtuali. Per intenderci, entrambi gli oggetti derivati ??da Polygon hanno una tabella di funzione virtuale che contiene i puntatori di funzione alle implementazioni di tutte le loro funzioni (non statiche); e quando si crea un'istanza di un triangolo, il puntatore di funzione virtuale per la funzione area () punta alla funzione Triangolo :: area (); quando si crea un'istanza di un rettangolo, la funzione area () punta alla funzione Rectangle :: area (). Poiché i puntatori a funzioni virtuali vengono archiviati insieme ai dati per un oggetto in memoria, ogni volta che si fa riferimento a tale oggetto come poligono, verrà utilizzata l'area appropriata () per quell'oggetto.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top