This shows you the differences between two versions of the page.
Next revision | Previous revision | ||
in204:cpp:syntax:class:virtual [2021/03/28 07:43] bmonsuez created |
in204:cpp:syntax:class:virtual [2022/11/18 10:51] (current) |
||
---|---|---|---|
Line 23: | Line 23: | ||
unsigned rgbValue24() const { return 0xFF0000; } | unsigned rgbValue24() const { return 0xFF0000; } | ||
}; | }; | ||
- | class Red: public Color | + | class Green: public Color |
{ | { | ||
public: | public: | ||
Line 51: | Line 51: | ||
<code cpp> | <code cpp> | ||
- | fillreact(Red(), 0, 0, 10, 10); | + | fillreact(Red(), 0, 0, 10, 10); |
</code> | </code> | ||
La sortie sera: | La sortie sera: | ||
Fill the area [0, 0, 10, 10] with color . | Fill the area [0, 0, 10, 10] with color . | ||
+ | |||
+ | Ce qui est parfaitement normal puisque lors de l'appel de la fonction ''name()'' : | ||
+ | <code cpp> | ||
+ | std::cout << "Fill the area [" << x << ", " << y << ", " << x + width << ", " << y + height | ||
+ | << "] with color " << theColor.name() << "." << std::endl; | ||
+ | </code> | ||
+ | |||
+ | 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 : | ||
+ | |||
+ | <code cpp> | ||
+ | class Color | ||
+ | { | ||
+ | public: | ||
+ | virtual std::string name() const { return ""; } | ||
+ | virtual unsigned rgbValue24() const { return 0; } | ||
+ | }; | ||
+ | </code> | ||
+ | |||
+ | 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 : | ||
+ | |||
+ | <code cpp> | ||
+ | fillreact(Red(), 0, 0, 10, 10); | ||
+ | </code> | ||
+ | |||
+ | 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()'' : | ||
+ | <code cpp> | ||
+ | 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; | ||
+ | } | ||
+ | </code> | ||
+ | |||
+ | Normalement, sans le mot clé ''virtual'', le compilateur aurait fait les opérations suivantes : | ||
+ | - quel est le type de l'objet ''theColor''. C'est ''Color''. | ||
+ | - on appelle alors la méthode ''Color::name()''. | ||
+ | |||
+ | Avec le mot clé ''virtual'', la compilateur fait les opérations suivantes : | ||
+ | - quel est type de l'objet ''theColor'' **au moment de sa création**. C'est ''Red''. | ||
+ | - 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 : | ||
+ | - les informations sur le type de l'objet au moment de la création. | ||
+ | - 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'' : | ||
+ | |||
+ | <code cpp> | ||
+ | class Color | ||
+ | { | ||
+ | public: | ||
+ | std::string name() const { return ""; } | ||
+ | unsigned rgbValue24() const { return 0; } | ||
+ | }; | ||
+ | </code> | ||
+ | |||
+ | 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 : | ||
+ | |||
+ | <code cpp> | ||
+ | class Color | ||
+ | { | ||
+ | public: | ||
+ | std::string name() const = 0; | ||
+ | unsigned rgbValue24() const = 0; | ||
+ | }; | ||
+ | </code> | ||
+ | |||
+ | 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 : | ||
+ | |||
+ | <code cpp> | ||
+ | Color basicColor; | ||
+ | </code> | ||
+ | |||
+ | 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. | ||
+ | |||
+ | |||