Salut a tous
Je suis entrain de lire des cours sur le langage C++ mais j’arrive pas a comprendre le role du mots clé virtuel
http://cpp.developpez.com/faq/cpp/?p...explicite_base
merci d’avance
Version imprimable
Salut a tous
Je suis entrain de lire des cours sur le langage C++ mais j’arrive pas a comprendre le role du mots clé virtuel
http://cpp.developpez.com/faq/cpp/?p...explicite_base
merci d’avance
Prends le cas suivant :
Te donnera l'affichage suivant :Code:
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 class A { public: int f() { cout << "A::f" << endl; } virtual int g() { cout << "A::g" << endl; } }; class B : public A { public: int f() { cout << "B::f" << endl; } virtual int g() { cout << "B::g" << endl; } }; int main() { A a1; B b; A& a2 = b; a1.f(); a1.g(); b.f(); b.g(); a2.f(); a2.g(); }
Comme B dérive A, on peut faire l'affectation suivante A& a2 = b. comme f n'est pas virtuelle, quand on appelle a2.f(), on appelle f de A. Comme g est virtuelle, quand on appelle a2.g(), on regarde le type "réel" (plutôt, le type runtime) de a2, c'est à dire B, et on appelle du coup a2.B::g().Code:
1
2
3
4
5
6 A::f A::g B::f B::g A::f B::g
En espérant que c'est plus clair maintenant.
A ne pas négliger en POO les méthodes virtuel c'est vraiment très pratiques.
PS: Très bonne explication de white_tentacle ;)
Salut,
Le mot clé virtual (attention, bien que l'on parle d'une méthode virtuelle en francais, le mot clé est virtual (en anglais) ;)) a pour but de prévenir le compilateur que le comportement impliqué par la méthode est susceptible d'être modifié en fonction du type réel de l'objet au départ duquel on invoque cette méthode.
En effet, il arrive que tu veuille disposer d'un type de données qui peut être considéré comme la spécialisation d'un type moins "spécialisé".
Par exemple, un chien pourrait être considéré comme la spécialisation d'un type "animal", car le chien est un animal, mais certains de ses comportements (par exemple un comportement générique que nous nommerions "crier") sont propres... au chien (un chien aboie alors qu'un chat... miaule :D)
Il arrive aussi que tu souhaite pouvoir faire cohabiter plusieurs animaux de types différents, en disposant d'une collection dans laquelle tu aurait des chiens, des chats, des éléphants et des corbeaux.
Pour arriver à faire cohabiter tous ces animaux dans une même collection, il faut qu'ils puissent tous être considérés comme... des animaux (de manière plus générique que chiens, chats, éléphants ou autres).
Tu va donc mettre en oeuvre une relation dite "d'héritage" entre, d'un coté, ta classe animaux et de l'autre tes classes "spécialisées" (chien, chat, ...).
Mais il est vraisemblable, si tu demande à chaque animal présent dans cette collection de crier, que tu souhaites que les chiens aboient, que les chats miaulent, que les éléphants barrissent et que les corbeaux croassent... Et c'est là que le mot clé virtual va intervenir.
En effet, pour que tous tes animaux soient en mesure de crier, il faut que tu prévienne le compilateur qu'il existe un tel comportement dans la classe animaux.
Mais tu dois aussi prévenir le compilateur que ce comportement est susceptible d'être modifié en fonction du type réel de l'animal auquel tu demande de crier.
Ainsi, dans la classe animal, tu pourrais prévoir un comportement qui s'applique à tous, comme
Ca pourrait suffire à tous les animaux (même si on peut se poser la question de ce qu'est le cri d'un poisson rouge :D) mais, il faut avouer que cela manque un tout petit peu de précision...Code:
1
2
3
4
5
6
7
8 class Animal { public: void crier() const { std::cout<<"Pousse son cri"; } };
L'idéal serait que ce comportement soit utilisé sauf si on indique autre chose de "plus adapté" par rapport au type réel de l'animal.
Nous allons donc prévenir le compilateur que ce comportement est susceptible d'être modifé.
La classe Animal deviendrait donc
et nous allons créer plusieurs spécialisations d'animal, pour correspondre aux animaux que nous voulons gérer, par exemple:Code:
1
2
3
4
5
6
7
8 class Animal { public: virtual void crier() const { std::cout<<"Pousse son cri"; } };
un chine
un chatCode:
1
2
3
4
5
6
7
8
9 class Chien : public Animal { public: /* nous voulons que le chien aboie */ virtual void crier() const { std::cout<<"aboie méchamment"; } };
Une souris... Elle a aussi un cri (un couinement, je crois), mais "pousse son cri" me va très bienCode:
1
2
3
4
5
6
7
8
9
10 class Chat : public Animal { public: /* nous voulons que le chat miaule */ virtual void crier() const { std::cout<<"miaule tendrement"; } };
et nous pourrions prévoir une foule d'autres animaux (éléphants, corbeaux, dauphins, panthère, ...) et décider de spécialiser, ou non, le comportement qui consiste en "crier" pour chacun d'eux.Code:
1
2
3
4
5
6
7
8 class Souris : public Animal { public: /* Le comportement prévu pour la classe de base correspond à ce que * je veux... je ne dois donc pas le redéfinir pour la souris */ };
De cette manière, si on crée une collection d'animaux, par exemple
que nous y mettons différents animauxCode:std::vector<Animal> tab;
et que nous leur demandons tour à tour de crierCode:
1
2
3
4
5
6
7 tab.push_back(Elephant()); tab.push_back(Chien()); tab.push_back(Chat()); tab.push_back(Souris()); tab.push_back(Panthere()); tab.push_back(PoissonRouge()); /*...*/
tous les animaux pourront répondre, soit en adaptant le comportement crier selon leur type réel, soit en utilisant le comportement prévu par défaut ("pousse son cri")Code:
1
2 for(std::vector<Animal>::iterator it=tab.begin();it!=tab.end():++it) (*it).crier();
Pour être plus précis :
D'autant que c'est avec les pointeurs (et le polymorphisme) que le mot clé virtual prend tout son sens.Code:
1
2
3
4
5
6 vector<Animal*> tab; tab.push_back(new Chat()); tab.push_back(new Souris()); ...
Oui faut le passer sous forme de pointeurs, mais là, tu as une fuite mémoire. Comment sont détruits tes objets.
Il aurait fallu faire:
Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 std::vector<Animal*> tab; Chien* chien = new Chien(); tab.push_back(chien); Chat* chat = new Chat(); tab.push_back(chat); Souris* souris = new Souris(); tab.push_back(souris); for (std::vector<Animal*>::iterator it = tab.begin(); it != tab.end(); ++it) (*it)->crier(); delete souris; delete chat; delete chien;
Pas forcément...
Tu peux très bien invoquer delete sur la valeur d'un itérateur, si ton conteneur contient des pointeurs ;)
un code du genre de
Evitera les fuites de mémoiresCode:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 int main() { vector<Animal*> tab; tab.push_back(new Chat()); tab.push_back(new Souris()); /* ...*/ // faisons crier les animaux for(vector<Animal*>::iterator it = tab.begin();it!=tab.end();++it) (*it)->crier(); // libérons correctement la mémoire for(vector<Animal*>::iterator it = tab.begin(); it!=tab.end();++it) { delete (*it); // nous pourrions même prévoir de remettre le pointeur à null ;) (*it) = NULL; } //si nous avons remis les pointeurs à null std::remove_if(tab.begin(), tab.end(), isNull); }
Le tout avec une fonction libre isNull qui prend la forme de
que nous pourrions même généraliser sous la forme deCode:
1
2
3
4 bool isNull(Animal* an) { return an==NULL; }
Code:
1
2
3
4
5
6 template <typename T> bool isNull(T* t) { return t==NULL; }
Exact, je n'y avais pas pensé. C'est vrai que les adresses mémoires sont stockées dans le vecteur.
Salut tt le monde !
je donnerai plus un exemple concret :
Dans le MFC il existe une class qui s'appelle CButton, qui possede une methode DrawItem(LPDRAWITEMSTRUCT lpDIS) (si je m'en souviens bien) qui est une methode Virtuelle, cette methode est reprise par une autre classe CBitmapButton en mode virtuel. ensuite tu peux utiliser cette fonction pour redessiner la forme du bouton.
Pour info, comment on fait appel à la forme template de isNull?
J'essaie de refaire l'exemple, mais je n'y arrive pas.
De plus, j'affiche la taille du tableau après le remove_if(), et j'ai toujours 3 :?
Code:
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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87 #include <iostream> #include <vector> #include <algorithm> class Animal { public: virtual ~Animal() {} virtual void crier() const { std::cout<<"Pousse son cri"<<std::endl; } }; class Chien : public Animal { public: ~Chien() { std::cout<<"Destructeur de Chien"<<std::endl; } /* nous voulons que le chien aboie */ virtual void crier() const { std::cout<<"aboie mechamment"<<std::endl; } }; class Chat : public Animal { public: ~Chat() { std::cout<<"Destructeur de Chat"<<std::endl; } /* nous voulons que le chat miaule */ virtual void crier() const { std::cout<<"miaule tendrement"<<std::endl; } }; class Souris : public Animal { public: ~Souris() { std::cout<<"Destructeur de Souris"<<std::endl; } /* Le comportement prévu pour la classe de base correspond à ce que * je veux... je ne dois donc pas le redéfinir pour la souris */ }; bool isNull(Animal* an) { return an==NULL; } int main() { std::vector<Animal*> tab; tab.push_back(new Chien()); tab.push_back(new Chat()); tab.push_back(new Souris()); for (std::vector<Animal*>::iterator it = tab.begin(); it != tab.end(); ++it) { (*it)->crier(); delete *it; *it = NULL; } //si nous avons remis les pointeurs à null std::remove_if(tab.begin(), tab.end(), isNull); std::cout << "Taille: " << tab.size() << std::endl; return 0; }
Salut,
essaye ça;
Resultat : avec g++ MinGWCode:
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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87 #include <iostream> #include <vector> #include <algorithm> class Animal { public: virtual ~Animal() {} virtual void crier() const { std::cout<<"Pousse son cri"<<std::endl; } }; class Chien : public Animal { public: ~Chien() { std::cout<<"Destructeur de Chien"<<std::endl; } /* nous voulons que le chien aboie */ virtual void crier() const { std::cout<<"aboie mechamment"<<std::endl; } }; class Chat : public Animal { public: ~Chat() { std::cout<<"Destructeur de Chat"<<std::endl; } /* nous voulons que le chat miaule */ virtual void crier() const { std::cout<<"miaule tendrement"<<std::endl; } }; class Souris : public Animal { public: ~Souris() { std::cout<<"Destructeur de Souris"<<std::endl; } /* Le comportement prévu pour la classe de base correspond à ce que * je veux... je ne dois donc pas le redéfinir pour la souris */ }; bool isNull(const Animal* an) { return an==NULL; } int main() { std::vector<Animal*> tab; tab.push_back(new Chien()); tab.push_back(new Chat()); tab.push_back(new Souris()); for (std::vector<Animal*>::iterator it = tab.begin(); it != tab.end(); ++it) { (*it)->crier(); *it = NULL; } //si nous avons remis les pointeurs à null tab.erase (std::remove_if(tab.begin(), tab.end(), isNull), tab.end ()); std::cout << "Taille: " << tab.size() << std::endl; return 0; }
Code:
1
2
3
4
5aboie mechamment miaule tendrement Pousse son cri Taille: 0
Pour ça, c'est réglé.
Et pour l'appel du pédicat isNull() templétisé?
Au boulot, je dois développer en Win32, mais j'ai envie d'apprendre le standard qui est plus interressant à mon humble avis.
"Mon humble avis" Fausse medestie, aha je plaisante,
Pour Win32 tu peux lire le bouquin "Programming Windows", c'est tout en C c'est tres interressant :mrgreen:
Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 template <class T> bool isNull(T* t) i{return t==NULL;} foo(){ Animal* a = new Animal(); // Ces 2 écritures sont valables, la première permet de lever des ambigüités dans certaines situations. bool b1 = isNull<Animal>(a); bool b2 = isNull(a); vector<Animal*> zoo; zoo.push_back(new Lion); for(vector<Animal*>::iterator it = zoo.begin(); it!=zoo.end(); ++it) bool b3 = isNull(*it); // delete & Cie }
Un conteneur de pointeurs c'est le bon truc pour faire du code non exception-safe...
Un delete (ou toute autre libération de ressource), ça doit être dans un destructeur. Quand est-ce que les gens comprendront ça ?
Ici c'est seulement pour montrer des exemples sur un point précis qui n'est pas directement lié à la gestion de la mémoire.
Mais il y a des cas où les conteneurs de pointeurs sont inévitables ; par exemple :
- si on veut tirer pleinement partie du polymorphisme ;
- si l'objet à stocker ne dispose pas d'un constructeur par copie (constructeur par copie déclaré private).
Mais même dans ces cas là je ne conçois pas de ne pas les encapsuler dans une classe pour assurer le RAII. D'autant que quand on doit gérer des objets de cette façon, c'est généralement qu'il y a un concept fort qui englobera le problème qui sera la réelle interface, le vector de pointeur sera seulement de l'implémentation interne.