User Tools

Site Tools


cpp:syntax:class:deriving:virtual_methods

Méthodes virtuelles & Polymorphismes

Pourquoi les méthodes virtuelles

Nous considérons une classe Color qui fournit deux méthodes, une méthode name() qui retourne le nom de la couleur et une méthode rgbValue() retourne les composants RGB de la couleur (Rouge, Vert & Bleue) au format 24 bits.

class Color
{
public:
    std::string name() const { return ""; }
    unsigned rgbValue24() const { return 0; }
};

Nous souhaitons hériter que les objets Blue, Red et Green qui héritent de cette classe redéfinisse les fonctions name et rgbValue24. L'objectif et que ces fonctions fournissent la bonne couleur.

class Red: public Color
{
public:
    std::string name() const { return "red"; }
    unsigned rgbValue24() const { return 0xFF0000; }
};
class Green: public Color
{
public:
    std::string name() const { return "green"; }
    unsigned rgbValue24() const { return 0x00FF00; }
};
class Blue: public Color
{
public:
    std::string name() const { return "blue"; }
    unsigned rgbValue24() const { return 0x0000FF; }
};

Nous souhaitons désormais pouvoir utiliser ces couleurs pour remplir une surface:

void fillrect(const Color& theColor, int x, int y, int width, int height)
{
    std::cout << "Fill the area [" << x << ", " << y << ", " << x + width << ", " << y + height 
        << "] with color " << theColor.name() << "." << std::endl;
} 

Cependant, si nous exécutons le code suivant :

fillreact(Red(), 0, 0, 10, 10);

La sortie sera:

Fill the area [0, 0, 10, 10] with color .

Ce qui est parfaitement normal puisque lors de l'appel de la fonction name() :

    std::cout << "Fill the area [" << x << ", " << y << ", " << x + width << ", " << y + height 
        << "] with color " << theColor.name() << "." << std::endl;

le type de theColor est Color. Donc le compilateur va appelé la méthode Color::name() et non pas la méthode Red::name() bien que l'objet a pour type effectif Red.

Nous voyons une différence entre deux notions de type:

  • le type réel de l'objet (celui lors de la création) qui est Red dans le cas précédent;
  • le type de manipulation de l'objet (celui au moment ou l'on appelle la fonction) qui est Color dans le cas précédent.

Par défaut, la méthode appellée est celle qui est définie dans le type de manipulation et non pas le type de l'objet lors de la création. Cependant, cela n'est pas satisfaisant. Nous aimerions pouvoir remplacer dans le type Color la méthode name() par la méthode name() définie dans le type Red.

Définir une méthode virtuelle

En reprenant l'exemple précédent, nous pouvons introduire le mot clé virtual devant chacune des deux méthodes qui ont été définies :

class Color
{
public:
    virtual std::string name() const { return ""; }
    virtual unsigned rgbValue24() const { return 0; }
};

Le fait de mettre le mot-clé virtual devant chacune des méthodes indique que ces méthodes sont substituables. Ceci signifie que désormais l'exécution de :

fillreact(Red(), 0, 0, 10, 10);

produira la sortie:

Fill the area [0, 0, 10, 10] with color Red.

Qu'est ce qui se produit lorsque l'on définit une méthode virtuelle

Ajouter la mot clé virtual indique au compilateur que la méthode qui doit être appelée est la méthode qui est définie par le type effectif (celui lors de la création) de l'objet.

Ainsi dans la fonction, nous avons l'appel theColor.name() :

void fillrect(const Color& theColor, int x, int y, int width, int height)
{
    std::cout << "Fill the area [" << x << ", " << y << ", " << x + width << ", " << y + height 
        << "] with color " << theColor.name() << "." << std::endl;
}

Normalement, sans le mot clé virtual, le compilateur aurait fait les opérations suivantes :

  1. quel est le type de l'objet theColor. C'est Color.
  2. on appelle alors la méthode Color::name().

Avec le mot clé virtual, la compilateur fait les opérations suivantes :

  1. quel est type de l'objet theColor au moment de sa création. C'est Red.
  2. on appelle alors la méthode Red::name().

Qu'est ce qui se passe quand on active la méthode virtuelle

Quand on ajouter une méthode virtuelle à une classe, on ajoute le stockage d'informations complémentaires. Ces informations complémentaires sont :

  1. les informations sur le type de l'objet au moment de la création.
  2. une table des méthodes virtuelles qui permet de savoir que pour la méthode name(), on appelle la méthode Red::name() et non pas la méthode Color::name().

Comment s'effectue donc l'appel d'une méthode method() en C++ :

  • Un objet object de type Object ne contient aucune méthode virtuelle. Alors on appelle la méthode Object::method().
  • Un objet object de type VirtualObject contient une ou plusieurs méthodes virtuelles. On regarde si la méthode est une méthode virtuelle.
    • La méthode n'est pas une méthode virtuel, alors on appelle la méthode VirtualObject::method().
    • La méthode est une méthode virtuelle, alors on détermine le type de l'objet au moment de la création. Supposons que l'objet est de type DerivedObject, dans ce cas, le compilateur recherche dans la table des méthodes virtuelles la méthode qui s'appelle method() à appeller. Supposons que la méthode method() a été redéfinie dans la classe DerivedObject, alors le compilateur va trouver dans la table des méthodes virtuelles la méthode pour l'entrée method(), la méthode DerivedObject::method(). Si la méthode n'a pas été redéfinie, alors le compilateur va trouver dans la table des méthodes virtuelles pour l'entrée method(), la méthode Object::method().

Définir une méthode virtuelle purement abstaite

Regardons les méthodes de la classe Color :

class Color
{
public:
    std::string name() const { return ""; }
    unsigned rgbValue24() const { return 0; }
};

Les méthodes name() et rgbValue24() retourne des valeurs par défaut. Ce qui n'est d'ailleurs pas souhaitable, puisque quelque part, ces valeurs n'ont pas de sens. Il est possible de définir une classe squelette, appellée aussi classe asbtraite dans laquelle les méthodes sont déclarées mais ne sont pas implantées. Pour la classe Color, cela revient à l'écriture suivante :

class Color
{
public:
    std::string name() const = 0;
    unsigned rgbValue24() const = 0;
};

La classe Color expose deux méthodes name() et rgbValue() qui sont deux méthodes virtuelles mais n'ayant aucun code. Cela impose aux classes qui héritent de la classe Color de devoir implanter les méthodes virtuelles.

De plus, une classe qui possède des méthodes virtuelles non définies (purement abstraite) ne peut pas être crée. En conséquence :

  Color basicColor;

génère une erreur à la compilation. En effet, vous essayez de créer une classe dont tous les éléments (ici en l'espèce ce sont les méthodes) ne sont pas définis. Ce qui est impossible. Le compilateur vous protège contre ce type d'erreur.

cpp/syntax/class/deriving/virtual_methods.txt · Last modified: 2022/11/18 10:47 (external edit)