2 votes

Comment le compilateur sait-il quelle fonction virtuelle appeler dans cette situation ?

J'étudie pour un examen et on me donne ce code :

#include <iostream>
#include <string>
#include <cmath>

using namespace std;

class Expression {
public:
    Expression() = default;
    Expression(const Expression&) = delete;
    Expression& operator=(const Expression&) = delete;
    virtual ~Expression() {}
    virtual double eval()const = 0;
    virtual void print(ostream& out)const = 0;
    friend ostream& operator<<(ostream& out, const Expression& e) {
        // cout << "@";
        e.print(out);
        return out;
    }
};

class BinaryExpression : public Expression {
    Expression* _e1, * _e2;
    char _sign;
    virtual double eval(double d1, double d2)const = 0;
public:
    BinaryExpression(Expression* e1, Expression* e2, char sign) : _e1(e1), _e2(e2), _sign(sign) {}
    ~BinaryExpression() override { delete _e1; delete _e2; }
    virtual double eval()const override {
        cout << "BE eval" << endl;
        return eval(_e1->eval(), _e2->eval());
    }
    virtual void print(ostream& out)const override {
        out << '(' << *_e1 << _sign << *_e2 << ')';
    }
};

class Sum : public BinaryExpression {
    virtual double eval(double d1, double d2)const override {
        cout << "Sum private eval" << endl;
        return d1 + d2;
    }
public:
    Sum(Expression* e1, Expression* e2) : BinaryExpression(e1, e2, '+') {}
};

class Exponent : public BinaryExpression {
    virtual double eval(double d1, double d2)const override {
        cout << "E private eval" << endl;
        return std::pow(d1, d2);
    }
public:
    Exponent(Expression* e1, Expression* e2) : BinaryExpression(e1, e2, '^') {}
};

class Number : public Expression {
    double _d;
public:
    Number(double d) : _d(d) {}
    virtual double eval()const override {
        cout << "Num eval" << endl;
        return _d;
    }
    virtual void print(ostream& out)const override {
        out << _d;
    }
};

int main() {
    Expression* e = new Sum(
        new Exponent(
            new Number(2),
            new Number(3)),
        new Number(-2));

    cout << *e << " = " << e->eval() << endl;
    delete e;
}

J'ai utilisé le débogueur pour voir quelles lignes sont exécutées, mais je me demande toujours comment le compilateur a su quelle fonction appeler à chaque fois dans la section main() où nous appelons e->eval()

Sortie :

BE eval
Num eval
BE eval
Num eval
Num eval
E private eval
Sum private eval
((2^3)+-2) = 6

Étant donné que chaque classe a une fonction d'évaluation, certaines en ont même deux et l'utilisation du pointeur d'expression m'a un peu déstabilisé. Que recherche exactement le compilateur lorsqu'il cherche quelle fonction eval() à exécuter à chaque fois ?

4voto

freakish Points 20067

Il y a deux sujets dans votre question :

Comment le compilateur sait-il quelle fonction utiliser, étant donné que certaines classes ont plusieurs fonctions avec le même nom ?

Eh bien, ils peuvent avoir le même nom, mais ils n'ont pas le même signature . Il n'y a aucune ambiguïté entre eval() y eval(x,y) car il n'y a qu'un seul eval qui n'accepte aucun argument et seulement un eval qui accepte deux arguments.

Étant donné que Expression* e comment le compilateur sait-il quelle fonction appeler en e->eval() expression ?

La réponse est que le compilateur ne le sait pas. Cela se produit à temps de fonctionnement mais pas pendant la compilation. À moins qu'une technique d'optimisation avancée, appelée dévirtualisation, ne s'applique (ce qui est un sujet important que je ne vais pas aborder ici).

Typiquement 1 lorsque vous définissez des fonctions virtuelles sur une classe, votre compilateur stockera des données supplémentaires dans chaque objet de ce type, appelées vtable qui est juste un tableau de pointeurs de fonctions. Ensuite, lorsque vous faites e->eval() sur une méthode virtuelle, le compilateur remplacera cet appel par deux étapes : (1) récupérer le pointeur de fonction à partir de la vtable stockée dans le fichier e correspondant à eval (2) appeler ce pointeur de fonction avec la méthode virtuelle e (et potentiellement d'autres arguments).

<sup>1 </sup>Il s'agit d'un détail de mise en œuvre, l'une des stratégies possibles, pas nécessairement ce qui se passe exactement.

2voto

JVApen Points 4523

Les fonctions virtuelles fonctionnent sur la base d'un VTableau .

En bref, lors de la déclaration de votre classe, il faut en fait que la classe ait un tableau de pointeurs vers les fonctions réelles. Au lieu d'un appel normal à la fonction, elle récupère d'abord cette adresse dans le tableau et l'appelle ensuite.

En pratique, les classes virtuelles ont un pointeur supplémentaire dans la classe qui fait référence à ce tableau (le VTable) et lorsque votre classe est construite, ce pointeur est rempli avec l'adresse du VTable de cette classe (calculé au moment de la compilation).

Le modèle mental simple est donc le suivant : il s'agit de pointeurs vers des fonctions plutôt que vers des données, avec une certaine magie du compilateur derrière.

Si vous voulez vraiment, vous pouvez émuler cela en C en utilisant des pointeurs de fonction et beaucoup de code manuel. Ce serait certainement un bon exercice pour apprendre à les connaître.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X