Bonjour,
Apres plusieurs recherche, je ne parviens pas à comprendre le fonctionnement du mot clef "virtual". Quelqu'un aurait-il un exemple très simple (j'insiste vraiment je suis très novice) de son utilisation, s'il vous plait?
Merci par avance.
Bonjour,
Apres plusieurs recherche, je ne parviens pas à comprendre le fonctionnement du mot clef "virtual". Quelqu'un aurait-il un exemple très simple (j'insiste vraiment je suis très novice) de son utilisation, s'il vous plait?
Merci par avance.
Ce qu'il permet, d'un point de vue syntaxe :
D'un point de vue utilité, il permet de gérer des objets différents par l'intermédiaire d'une interface commune, tout en permettant à ces objets d'effectuer des actions différentes pour réaliser cette interface.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 class A { void f() {cout << "A::f" << endl;} virtual void g() {cout << "A::g" << endl;} }; class B : public A { void f() {cout << "B::f" << endl;} virtual void g() {cout << "B::g" << endl;} }; int main() { B b; A* pa = &b; B* pb = &b; pa->f(); pa->g(); pb->f(); pb->g(); }
j'obtiens comme résultats:
je m'attendais à obtenir:A::f
B::g
B::f
B::g
Pourquoi c'estA::f
A::g
B::f
B::gqui intervient?
Code : Sélectionner tout - Visualiser dans une fenêtre à part virtual void g() {cout << "B::g" << endl;}
Est-on obliger d'écrire 2 fois virtual? ou une seule devantaurait suffit?
Code : Sélectionner tout - Visualiser dans une fenêtre à part virtual void g() {cout << "B::g" << endl;}
Hello,
Une méthode virtuelle peut être redéfinie dans les classes filles.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5 B b; A* pa = &b; B* pb = &b; pa->f(); // f() non virtuelle, donc appel de A::f(); pa->g(); // g() virtuelle, pa est de type A* (typage statique) mais pointe sur un objet de type B (typage dynamique), c'est donc B::g() qui est appelée.Il faut bien l'écrire 2 fois.Est-on obliger d'écrire 2 fois virtual? ou une seule devant
(Au minimum dans la classe de base (A ici), il me semble pas qu'indiquer "virtual" ou non dans les classes dérivées ne change quelque chose).
Parce que g est virtual
Quand tu considère pa, son type statique, la manière dont il est déclaré dans le programme, c'est pointeur sur un objet de type A. Mais en réalité, il pointe sur un objet de type B. Si on appelle une fonction classique sur pa, c'est son type statique qui va déterminer quelle fonction est vraiment appelée. Si la fonction est virtuelle, on va regarder le type de l'objet sur lequel on pointe réellement pour déterminer la fonction appelée.
Ni l'un, ni l'autre : Une seule fois est nécessaire, mais au niveau de la classe de base. Ceci dit, il est généralement utile quand on lit une classe dérivée de savoir si on redéfini une fonction de la classe de base ou pas. Il est donc préférable de re-préciser virtual dans la classe dérivée, ou en C++11 d'utiliser le mot clef override pour cela.
ok!! Je crois que je comprends ou vous voulez en venir.
Un typage dynaique est un typage qui se préoccupe du type réel de l'objet?
Je ne comprends pas...Ceci dit, il est généralement utile quand on lit une classe dérivée de savoir si on redéfini une fonction de la classe de base ou pas. Il est donc préférable de re-préciser virtual dans la classe dérivée
Oui.
Quand dans une classe dérivée, on déclare une fonction ayant le même nom et les mêmes arguments qu'une fonction virtuelle de la classe de base, on dit qu'on redéfinit la fonction de la classe de base (puisque du point de vue de la classe dérivée, tout se passera comme si la fonction de la classe de base avait été remplacée par la nouvelle version). Dit plus simplement, virtual dans la classe dérivée n'est pas obligatorie, mais recommandé (ou alors, l'utilisation d'override).
Merci, cette fois j'ai compris.
Une petite precision tout de meme. Virtual permet de la flexibilité dans les cas ou on ne peut pas connaitre à l'avance le type réel de l'objet?
Dans notre exemple, au lieu d'appeler pa g(), on aurait pu directement appeler pbg()? Cela aurait été plus rigoureux?
virtual permet de trimballer un pointeur sur une classe de base, et lors de l'appel d'une méthode appliqué à ce pointeur, d'appeler en réalité la méthode implémentée dans l'objet dérivé.
C'est par exemple pratique quand tu as un vecteur de pointeurs vers une classe de base, et que tu souhaites appeler un même nom de méthode pour chacun des éléments.
Selon le type réel (déterminé dynamiquement), la bonne méthode (= la bonne définition) sera appelée.
Salut,
Le mot clé virtual indique au compilateur que l'on risque de redéfinir le comportement de la fonction dans les classes dérivées. Il a pour résultat de mettre en place toute une mécanique qui permet de le faire.
Dans une relation d'héritage (il n'a de sens qu'avec une telle relation), il permet d'obtenir ce que l'on appelle un comportement polymorphe, c'est à dire un comportement qui s'adaptera au type réel de la donnée manipulée même s'il s'agit d'une référence (ou un pointeur) sur la classe de base. C'est la base de la substituabilité, qui est l'ajout essentiel de la programmation orientée objet par rapport à la programmation purement procédurale![]()
Hormis pour jouer des conversions implicites, ça pourrait etre une bonne idée de virtualiser toutes ses fonctions? Ou ce n'est pas a faire pour eviter les erreurs et garder en lisibilité?
Salut,
tout virtualiser, c'est le parti pris de certains langages. Pour ma part, je suis contre.
Un virtual ça a un coût, pas forcément énorme, souvent négligeable, mais ça a un coût. Et ça reste une fonctionnalité qui modifie un peu ce que fait ton code.
La philosophie C++ est que tu ne payes que ce que tu écris/demandes/codes. Ce que je trouve vraiment bien AMHA.
Bousk a tout à fait raison. Certains langages ont -- par nature -- besoin que toutes les fonctions soient virtuelles. Mais pas C++.
Non seulement le fait de virtualiser une fonction a un cout en termes d'exécution, mais il faut te dire que la présence du mot clé virtual a une signification pour l'utilisateur de la classe, très semblable à la signification qu'il prend pour le compilateur : je peux, en cas de besoin, redéfinir le comportement de la fonction pour qu'il soit plus adapté au type réel de l'objet.
Pour certaines fonctions, cela a du sens de donner cette possibilité, pour d'autres, cela n'en a absolument pas![]()
Merci pour vos réponses.
On rentre ici dans un domaine que je n'est encore jamais abordé. Aurais tu un exemple très simple à comprendre?je peux, en cas de besoin, redéfinir le comportement de la fonction pour qu'il soit plus adapté au type réel de l'objet.
Si tu n'as pas encore abordé le problème, il vaut peut etre mieux attendre que tu aies un peu avancé dans le cours/le tutoriel que tu suis.
Pour arriver à comprendre le principe, il faut que tu comprennes ce qu'est un héritage. Les choses deviendront claires d'elles meme une fois que tu auras abordé cette partie de ton cours / tutoriel. Mais, entre temps, cela ne ferait que te déconcerter d'avantage![]()
Déconcentré moi?
J'ai vu les héritages. une personne hors forum m'a répondu. apparement, il serait question de choisir la bonne fonction entre 2 fonctions virtualisées. C'est ça la modif de la fonction?
B.A.-BA des cours de polymorphisme, exemple typique
http://lmgtfy.com/?q=polymorphisme
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14 struct Animal { virtual void cri() const=0; }; struct Chat : Animal { virtual void cri() const { std::cout<<"miaou"<<std::endl; } }; struct Chien : Animal { virtual void cri() const { std::cout<<"wouaf"<<std::endl; } }; int main() { Animal* p = new Chat; p->cri(); delete p; p = new Chien; p->cri(); delete p; return 0; }
Bon, l'héritage public est la relation la plus forte qui puisse exister entre deux classes. C'est une relation EST-UN, au sens sémantique du terme (une voiture ou un camion EST-UN véhicule, un rectangle, un cercle ou un triangle EST-UN(e) forme géométrique, ...).
Il y a certaines règles à respecter pour pouvoir utiliser l'héritage, ce qui fait que, contrairement à ce que l'on apprend à l'école, la classe Carré ne peut pas hériter de la classe Rectangle, mais nous y reviendrons un peu plus tard si besoin en est
La classe de base (Vehicule ou FormeGeometrique, pour respecter les deux exemples que j'ai cités) présente un certain nombre de comportements communs à l'ensemble des classes qui peuvent en dériver.
Ainsi, la classe FormeGeometrique pourrait présenter des comportements comme print(), superficie() ou encore perimetre(). Mais tu t'attends à ce que ces comportements s'adaptent au type réel de la forme géométrique : tu t'attends à ce que la fonction print() affiche "rectangle" ou "cercle" ou "triangle" selon le cas, et tu t'attends à ce que les fonctions superficie et perimetre te renvoient une valeur correctement calculée. C'est ce que l'on appelle avoir un "comportement polymorphe" (un comportement qui s'adapte au type réel de l'objet manipulé).
Pour obtenir un tel comportement, on va déclarer les fonctions print, superficie et perimetre virtual, afin d'indiquer au compilateur que l'on risque de redéfinir le comportement de ces fonctions. Et comme il est impossible de calculer la superficie ou le périmètre d'une forme géométrique dont on ignore tout, nous déclarerons les fonctions perimetre et superficie comme étant virtuelles pures. Cela indique au compilateur que nous ne disposons, au niveau de la classe de base, pas des informations qui permettent de définir un comportement cohérent et que l'on ne définit donc pas le comportement pour ces fonctions.
Cela va transformer la classe de base en une classe abstraite, qui ne pourra pas être instanciée en tant que telle. Et, pour être en mesure d'instancier les classes dérivées, il faut que les deux fonctions soient correctement définies à chaque fois. au final, nous aurons quelque chose comme
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55 class FormeGeometrique{ public: virtual ~FormeGeometrique(){} virtual void print() const{std::cout<<"Forme Geometrique, sans precision"<<std::endl;} /* il n'existe aucun comportement "cohérent" pour calculer la superficie d'une forme géométrique * dont on ignore tout */ virtual double superficie() const = 0; /* il n'existe aucun comportement "cohérent" pour calculer le périmètre d'une forme géométrique * dont on ignore tout */ virtual double perimetre() const = 0; }; class Cercle : public FormeGeometrique{ public: Cercle(double diametre):diametre_(diametre){} virtual void print() const{std::cout<<"Cercle"<<std::endl;} virtual double superficie() const{ return 3.1415926 * diametre_*diametre_/4; } virtual double perimetre() const{ return 3.1415926 *diametre_; } private: double diametre_; }; class Rectangle : public FormeGeometrique{ public: Cercle(double hauteur, double largeur):hauteur_(hauteur), largeur_(largeur){} virtual void print() const{std::cout<<"Rectangle"<<std::endl;} virtual double superficie() const{ return hauteur_* largeur_; } virtual double perimetre() const{ return (hauteur_+largeur_)*2; } private: double hauteur_; double largeur_; }; class Triangle : public FormeGeometrique{ public: Cercle(double base, double hauteur):base_(base),hauteur_(hauteur){} virtual void print() const{std::cout<<"Triangle"<<std::endl;} virtual double superficie() const{ return hauteur_* largeur_/2; } virtual double perimetre() const{ /* ... */ } private: double base_; double hauteur_; };
Super réponses merci!
Je ne connais pas encore les règles de l'héritage. Est-ce complexe?
Ce n'est pas complexe, mais elles doivent être appliquées avec la plus grande rigueur.
La première chose à voir est qu'il faut effectivement pouvoir dire, du pur point de vue du dictionnaire, qu'un objet du type dérivé est "une sorte" d'objet du type de base (ex: une voiture est une sorte de véhicule)
Une fois cette première étape franchie, il reste la plus importante : respecter les règles de programmation par contrat ;
- Les préconditions ne peuvent pas être renforcées dans le type dérivé
- les postconditions ne peuvent pas être assouplies dans le type dérivé
- Les invariants doivent être respectés
Il faut considérer tout membre et toute fonction membre (quelle que soit la visibilité) comme étant un invariant.
Pour ce qui est des pré et postconditions, c'est ce qui t'empêche de faire dériver ListeTriee de Liste ou Carre de Rectangle : il y a des conditions beaucoup plus fortes dans ListeTriee ou dans Carre que dans Liste ou Rectangle : la liste triée doit être triée avant insertion d'un nouvelle élément et doit être triée après, et un carré doit forcément avoir quatre cotés égaux. Liste et Rectangle ne sont pas soumis à ces restrictions![]()
Vous avez un bloqueur de publicités installé.
Le Club Developpez.com n'affiche que des publicités IT, discrètes et non intrusives.
Afin que nous puissions continuer à vous fournir gratuitement du contenu de qualité, merci de nous soutenir en désactivant votre bloqueur de publicités sur Developpez.com.
Partager