Comment le compilateur C ++ sait-il quelle implémentation d'une fonction virtuelle à appeler?

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

  •  03-07-2019
  •  | 
  •  

Question

Voici un exemple de polymorphisme tiré de http://www.cplusplus.com/doc /tutorial/polymorphism.html (modifié pour la lisibilité):

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

Ma question est la suivante: comment le compilateur sait-il que ppoly1 est un rectangle et que ppoly2 est un triangle, de sorte qu'il puisse appeler la fonction area () correcte? Il pourrait le découvrir en consultant le "Polygon * ppoly1 = ?". ligne et sachant que rect est un rectangle, mais cela ne fonctionnerait pas dans tous les cas, n'est-ce pas? Et si vous faisiez quelque chose comme ça?

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

En supposant que vous êtes autorisé à accéder à cette zone aléatoire de la mémoire.

Je voudrais tester ceci mais je ne peux pas sur l'ordinateur sur lequel je suis en ce moment.

(J'espère que je ne manque pas quelque chose d'évident ...)

Était-ce utile?

La solution

Chaque objet (appartenant à une classe avec au moins une fonction virtuelle) a un pointeur appelé vptr . Il pointe vers le vtbl de sa classe actuelle (chaque classe avec des fonctions virtuelles en possède au moins une; éventuellement plusieurs pour certains scénarios à héritages multiples).

Le vtbl contient un ensemble de pointeurs, un pour chaque fonction virtuelle. Ainsi, au moment de l'exécution, le code utilise simplement le vptr de l'objet pour localiser le vtbl , et à partir de là l'adresse de la fonction remplacée réelle.

Dans votre cas particulier, Polygon , Rectangle et Triangle possède chacun un vtbl , chacun avec un entrée pointant sur la méthode area correspondante. Votre ppoly1 aura un vptr pointant sur du vtbl de , et ppoly2 de même avec le vtbl de Triangle . J'espère que cela aide!

Autres conseils

Chris Jester-Young donne la réponse de base à cette question.

Wikipedia est traité de manière plus approfondie.

Si vous souhaitez connaître tous les détails du fonctionnement de ce type d’objet (et pour tous les types d’héritage, y compris les héritages multiple et virtuel), l’une des meilleures ressources est celle de Stan Lippman " À l'intérieur du modèle d'objet C ++ ".

Abstraction faite des aspects de la liaison, ce n’est pas le compilateur qui le détermine.

C'est le runtime C ++ qui évalue, via vtables et vpointers, ce qu'est réellement l'objet dérivé au moment de l'exécution.

Je recommande fortement le livre de Scott Meyer, Effective C ++, pour une bonne description de la procédure.

Même comment les paramètres par défaut d'une méthode dans une classe dérivée sont ignorés et tous les paramètres par défaut d'une classe de base sont toujours pris! C'est contraignant.

Pour répondre à la deuxième partie de votre question: cette adresse n'aura probablement pas de table virtuelle au bon endroit et la folie s'ensuivra. En outre, il est indéfini selon la norme.

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

Ce code est une catastrophe imminente. Le compilateur va tout compiler correctement, mais en ce qui concerne l’exécution, vous ne pointerez pas sur une v-table valide et si vous avez de la chance, le programme ne fonctionnera plus.

En C ++, vous ne devez pas utiliser les anciens styles de style C comme celui-ci, mais plutôt dynamic_cast comme suit:

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

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

dynamic_cast retournera NULL si le pointeur donné n'est pas un objet Polygon valide et sera donc capturé par l'ASSERT.

Tables de fonctions virtuelles. En d'autres termes, vos deux objets dérivés de Polygon ont une table de fonctions virtuelle qui contient des pointeurs de fonction vers les implémentations de toutes leurs fonctions (non statiques). et lorsque vous instanciez un triangle, le pointeur de fonction virtuel de la fonction area () pointe vers la fonction Triangle :: area (); Lorsque vous instanciez un rectangle, la fonction area () pointe sur la fonction Rectangle :: area (). Les pointeurs de fonction virtuels étant stockés avec les données d'un objet en mémoire, chaque fois que vous faites référence à cet objet en tant que polygone, la zone () appropriée pour cet objet est utilisée.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top