• Aucun résultat trouvé

• L'appel du destructeur d'un objet d'une classe dérivée engendre l'appel automatique du destructeur de la classe de base.

• L'ordre d'exécution de ces destructeurs est inverse à l'ordre d'exécution des constructeurs : Exécution du destructeur de la classe dérivée puis exécution du destructeur de la classe de base

• D'une manière plus détaillée, la libération se fait comme suit : o Appel et exécution du destructeur de la classe dérivée. o Appel et exécution du destructeur de la classe de base.

o Libération de la mémoire utilisé par les attributs (Il ne s'agit pas des espaces mémoires dynamiques pointés par les éventuels attributs de type pointeurs mais des espaces occupés par les attributs eux mêmes).

Exemple :

class A { public : … … …

~A(){ cout<<"Destructeur de A"<<endl;} … … … }; class B : public A { public : … … … ~B(){ cout<<"Destructeur de B"<<endl;} … … … };

Héritage et polymorphisme Programmation orientée objet (C++) _________________________________________________________________________________________________________________

La destruction d'un objet déclaré comme suit :

B obj(5,7);

va engendrer la sortie suivante

… … …

Destructeur de B Destructeur de A

Polymorphisme

• Le mot polymorphisme désigne le fait de pouvoir prendre plusieurs formes.

• En programmation orientée objet le polymorphisme est un concept qui se manifeste par la capacité d'une méthode à s'exécuter de différentes manières selon la nature de l'objet qui l'appelle (objet de base ou objet dérivé).

• Le polymorphisme concerne essentiellement des objets liés par une relation d'héritage.

Limite de la liaison statique des méthodes

Exemple : Rappel de la liaison statique

class A {public : int att_A; A(int p) : att_A(p) { cout<<"Constructeur de A"<<endl; } void Afficher()

{ cout<<" att_A :"<<att_A<<endl; } };

class B : public A {public :

int att_B;

B(int p1,int p2) : A(p1),att_B(p2) {

cout<<"Constructeur de B"<<endl; }

void Afficher() {

cout<<" att_A :"<<att_A<<" et att_B :"<<att_B<<endl; } }; void main() { A* pa; B objB(3,5), *pb; pb=&objB; pb->Afficher(); pa=pb; pa->Afficher(); }

pb->Afficher(); engendre l'affichage suivant : att_A : 3 et att_B : 5

pa->Afficher(); engendre l'affichage suivant : att_A : 3

Héritage et polymorphisme Programmation orientée objet (C++) _________________________________________________________________________________________________________________

• L'affichage incomplet à partir du pointeur pa provient du fait que le compilateur se base sur le type de pa au moment de la compilation pour décider de la version de la méthode à appeler : pa étant de type A* alors c'est la méthode Afficher de la classe A qui sera appelée dans ce cas.

• Le compilateur effectue dans ce cas une liaison statique. Cette dernière ne tient pas compte du véritable type de l'objet pointé et ne permet pas par conséquent d'appeler les versions appropriées des méthodes à utiliser.

Liaison dynamique et polymorphisme

• Pour remédier au problème de la liaison statique rencontré lors de la conversion d'un objet dérivé vers un objet de base il faut être capable d'appeler la méthode qui correspond au type de l'objet pointé et non au type du pointeur. Pour ce faire il faut effectuer ce que l'on appelle une liaison dynamique entre l'appel et le corps de la méthode. Cette liaison doit être faite au moment de l'exécution et non au moment de la compilation.

• La liaison dynamique dote les méthodes de la capacité de se comporter de différentes manières selon l'objet appelant. Ces méthodes sont alors dites polymorphes.

Mise en œuvre du polymorphisme

• Pour rendre une méthode polymorphe, cette dernière doit être déclarée virtuelle dans la classe de base à l'aide du mot-clé virtual selon la syntaxe suivante :

virtual Type NomMethodePolymorphe(Paramètres) { … … … … } Exemple 1: class A {public : int att_A; A(int p) : att_A(p){}; virtual void Afficher()

{

cout<<" att_A :"<<att_A<<endl }

};

class B : public A {public :

int att_B;

B(int p1,int p2) : A(p1),att_B(p2){} void Afficher()

{

cout<<" att_A :"<<att_A<<" et att_B :"<<att_B<<endl; } }; void main() { A* pa; B objB(3,5), *pb; pb=&objB; pb->Afficher(); pa=pb; pa->Afficher();

Héritage et polymorphisme Programmation orientée objet (C++) _________________________________________________________________________________________________________________

pa->Afficher(); va engendrer dans ce cas l'affichage suivant : att_A : 3 et att_B : 5

Remarque :

La liaison dynamique d'une méthode n'est pas limitée au cas où cette dernière est appelée explicitement à partir d'un objet mais s'applique également au cas de l'appel de la méthode à l'intérieur de la classe par d'autres méthodes. L'exemple suivant illustre cette situation.

Exemple :

class A {public :

int att_A;

A(int p) : att_A(p){}; virtual void Contenu()

{

cout<<" att_A :"<<att_A<<endl; }

void Afficher() {

cout<<"Le contenu de l'objet est : "<<endl; Contenu(); } }; class B : public A {public : int att_B;

B(int p1,int p2) : A(p1),att_B(p2){} void Contenu()

{ cout<<" att_A :"<<att_A<<" et att_B :"<<att_B<<endl; } };

Considérations relatives à la déclaration et l'usage des fonctions virtuelles

• La spécification du mot-clé virtual n'est pas nécessaire pour les redéfinitions de la méthode virtuelle. Cette spécification peut se limiter à la classe de base.

• A partir du moment où une fonction a été déclarée virtuelle dans une classe de base, alors elle sera soumise à la liaison dynamique dans cette classe et dans toutes les classes dérivées. Il est à noter que l'effet de virtual ne se limite pas aux classes obtenues par dérivation directe mais s'étend à toute la hiérarchie des classes obtenues par dérivation successives.

• La redéfinition d'une fonction virtuelle n'est pas obligatoire dans toutes les classes dérivées. Toutefois une classe dérivée ne peut appeler une méthode virtuelle que si cette dernière possède au moins une définition dans sa hiérarchie. Dans ce cas c'est la dernière redéfinition qui sera utilisée.

• Si une méthode a été déjà définie dans une classe de base puis redéfinie comme virtuelle mais dans une classe dérivée, alors la nouvelle redéfinition sera soumise à la liaison dynamique. Toute autre redéfinition de cette méthode dans une classe dérivée sera également soumise à la liaison dynamique.

Héritage et polymorphisme Programmation orientée objet (C++) _________________________________________________________________________________________________________________

• La surdéfinition (et non la redéfinition) dans une classe dérivée d'une méthode déclarée virtuelle dans une classe de base est interprétée comme une nouvelle définition de la méthode (une définition possédant une signature et une syntaxe d'appel différentes). Elle ne sera pas par conséquent soumise à la liaison dynamique mais plutôt à la liaison statique.

Objets auto, pointeurs, références et possibilité de typage dynamique

• Le typage dynamique n'est possible qu'à partir des pointeurs et des références. Il ne l'est pas dans le cas des objets auto.

Cas des objets de type auto :

• La copie d'un objet auto d'une classe dérivée dans un objet auto de la classe de base engendre la copie membre par membre des attributs correspondant à la classe de base depuis l'objet dérivé vers l'objet de base.

• L'appel d'une méthode virtuelle depuis l'objet de base ainsi copié ne peut invoquer que la définition associée à la classe de base. En effet, il n'est pas concevable d'appeler la version redéfinie dans la classe dérivée car l'objet copié ne dispose dans sa copie d'aucune information sur les données dérivées.

Exemple :

Etant données les classes A et B dérivée de A.

A ObjA; B ObjB(5,7); ObjA = ObjB;

ObjA.Afficher(); ne peut invoquer que la version de base de Afficher. Il n'est pas

possible d'utiliser la version redéfinie car ObjA n'a aucune information sur les attributs dérivés (att_B dans ce cas).

Cas des pointeurs :

Lorsqu'il s'agit de pointeur la liaison dynamique est envisageable. En effet, dans le cas où ce pointeur pointe sur un objet dérivé, l'appel de la redéfinition d'une méthode virtuelle depuis ce pointeur ne risque pas de poser de problème puisque toutes les informations nécessaires au bon fonctionnement de la redéfinition sont nécessairement disponibles dans l'objet pointé.

Exemple :

Etant données les classes A et B dérivée de A.

B ObjB(5,7);

A* pa = (A*)&ObjB; pa->Afficher();

Il est envisageable en faisant une liaison dynamique de faire appel à la redéfinition de la méthode Afficher (version définie dans B). En effet, l'objet pointé dispose dans ce cas de toutes les informations nécessaires au bon fonctionnement de cette redéfinition.

att_A = 5 att_B = 7 att_A = 5 ObjB ObjA Copie att_A=5 att_B=7 EF0A78 ObjB A* pa

Héritage et polymorphisme Programmation orientée objet (C++) _________________________________________________________________________________________________________________

Cas des références :

Il est possible d'envisager une liaison dynamique avec les références pour les mêmes raisons que celles évoqués pour les pointeurs.

Exemple :

Etant données les classes A et B dérivée de A.

B ObjB(5,7); A& r = ObjB; r.Afficher();

Il est envisageable en faisant une liaison dynamique de faire appel à la redéfinition de la méthode Afficher (version définie dans B). En effet, l'objet référencé (ObjB) dispose dans ce cas de toutes les informations nécessaires au bon fonctionnement de cette redéfinition.

Restriction du polymorphisme

Les constructeurs ne peuvent pas être virtuels. Tout objet ne peut être construit que par le constructeur qui a été défini pour sa classe.

Polymorphisme des destructeurs

Les destructeurs peuvent être déclarés virtuels. Considérons le cas suivant :

class A {public : … … … ~A(){cout<<"Destructeur de A";} }; class B : public A {public : … … … ~B(){cout<<"Destructeur de B";} }; A* pa = new B(5,7); pa->Afficher(); delete pa;

• Si le destructeur de A n'a pas été déclaré comme virtuel dans A.

delete pa; va engendrer dans ce cas un appel au destructeur de A (liaison statique car pa

est un pointeur sur A et ~A n'est pas virtuel. Or ce destructeur n'est pas approprié pour détruire l'objet pointé puisque ce dernier est de type B.

Pour résoudre ce problème il faut modifier la classe A comme suit :

class A {public :

… … …

virtual ~A(){ … … … } };

delete pa; va engendrer dans ce cas un appel du destructeur de B (liaison dynamique).

Cet appel convient donc au type de l'objet pointé.

att_A=5 att_B=7 EF0A78 ObjB A& r att_A=5 att_B=7 EF0A78 A* p

Héritage et polymorphisme Programmation orientée objet (C++) _________________________________________________________________________________________________________________