Big Tuto : Apprenez le C++

Chapitre 27 : Polymorphisme (1)

 

Tutoriel présenté par : Robert Gillard (Gondulzac)
Date de publication : 22 février 2019
Date de révision : -

 

Retrouvez les projets complets de ce chapitre :

  

 

 

 Préliminaires

   Avec ce chapitre nous nous lançons un peu plus dans notre découverte de la POO. Avant d'entrer dans le polymorphisme proprement dit avec l'héritage multiple, nous allons continuer notre étude des méthodes virtuelles. Allez, le voyage continue, la route est longue mais on s'accroche. laughing



1.0 – L'Héritage simple (suite)

   1.1 – Résolution statique et résolution dynamique des liens

   L'exemple suivant va vous montrer ce qui est connu par le compilateur au moment de la compilation d'un programme par rapport à son exécution. Il s'agit de ce que l'on appelle la ''résolution statique'' et la ''résolution dynamique des liens''. Pas de panique, notre exemple va vous éclairer sur le sujet. wink

   Projet 158HeritageSimple_11

   Nous n'utiliserons qu'un seul fichier pour raccourcir quelque peu notre programme.
 

 

//Projet 158HeritageSimple_11
//Résolution statique et dynamique des liens (1)
//Fichier : HeritageSimple_11.cpp
 
#include <iostream>
 
//Déclaration de la classe Personnage
 
class Personnage
 
{
public:
 
//Constructeur et destructeur
Personnage() {};
virtual ~Personnage() {};
 
virtual void Se_Presenter() const
 
{
std::cout << "Je suis un personnage, le chef du village" << std::endl;
}
 
 
protected:
 
};
 
 
//Déclaration de la classe Guerrier
class Guerrier : public Personnage
{
public:
 
virtual void Se_Presenter() const
{
std::cout << "Je suis un guerrier, defenseur du village" << std::endl;
}
 
 
protected:
 
};
 
 
//Déclaration de la classe Archer
class Archer : public Personnage
{
public:
 
virtual void Se_Presenter() const
{
std::cout << "Je suis un archer, defenseur de la tour" << std::endl;
}
 
 
protected:
 
};
 
 
//Déclaration de la classe Druide
class Druide : public Personnage
{
public:
 
virtual void Se_Presenter() const
{
std::cout << "Je suis un druide, guerisseur du village" << std::endl;
}
 
 
 
protected:
 
};
 
 
//Déclaration de la fonction Presentation()
void Presentation(Personnage &p) //On passe une référence sur un personnage en argument
{
p.Se_Presenter();
}
 
 
 
int main()
{
 
Personnage unPersonnage;
Personnage *ptr = nullptr;
char choix('1');
 
std::cout << std::endl;
std::cout << "Resolution statique des liens : appel de la fonction Presentation()" << std::endl;
std::cout << "La liaison de l'objet a la fonction Presentation() se fait des la compilation : " << std::endl;
Presentation(unPersonnage);
 
std::cout << std::endl;
 
while (choix == '1' || choix == '2' || choix == '3' || choix == '4')
{
std::cout << "(1) Personnage" << std::endl;
std::cout << "(2) Guerrier" << std::endl;
std::cout << "(3) Archer" << std::endl;
std::cout << "(4) Druide" << std::endl;
std::cout << "Pressez un autre caractere pour Quitter" << std::endl <<
std::endl;
std::cout << "Votre choix : ";
std::cin >> choix;
 
switch (choix)
{
case '1':
ptr = new Personnage;
break;
case '2':
ptr = new Guerrier;
break;
case '3':
ptr = new Archer;
break;
case '4':
ptr = new Druide;
break;
 
default:
ptr = new Personnage;
break;
}
 
std::cout << "Resolution dynamique des liens : se fait pendant le deroulement du programme" << std::endl;
std::cout << "Appel de la fonction Se_Presenter() suivant choix" << std::endl;
ptr->Se_Presenter();
 
std::cout << std::endl;
 
}
 
std::cin.get();
delete ptr;
 
return 0;
} 

 

       Voyons tout d'abord comment ce programme est formé. Nous créons une classe de base Personnage, ainsi que trois classes dérivées Guerrier, Archer et Druide. Dans chacune des quatre classes, une méthode virtuelle Se_Presenter() est implémentée.

    A la suite de nos classes, nous définissions la fonction non virtuelle Presentation().

   On passe à la fonction main(). Après appel de la fonction Presentation(), on demande à l'utilisateur de faire un choix afin d'appeler une des quatre méthodes virtuelles.

   Dès la compilation du programme, la fonction Presentation() est connue, une référence à un objet personnage lui est passé en paramètre, le lien objet/fonction se fait à ce moment, c'est la résolution statique des liens. Celle-ci se fait donc à la compilation, avant l'exécution du programme. wink

   La fonction main() est intéressante en ceci que dans une boucle while on demande à l'utilisateur de choisir entre l'affichage d'une méthode de la présentation d'un personnage, d'un guerrier, d'un archer ou d'un druide (un pointeur sera créé dans le tas en fonction de notre choix).

   Remarquez qu'une fois le programme lancé, et avant qu'un choix ne se fasse, nous ne savons pas encore quels objets seront créés dans le tas ni quelles fonctions Se_Presenter() seront appelées. Ceci ne se fera qu'au moment où nous aurons fait ce premier choix et c'est à chacun de nos choix que s'exécutera la résolution dynamique des liens (l'appel de la bonne méthode).

   Cette différence entre résolution statique et résolution dynamique des liens est donc, avec cet exemple, facile à comprendre. wink

   Passons maintenant à la classe de base Personnage. Vous remarquerez que nous avons déclaré ''virtual'' le destructeur de Personnage. Dans le paragraphe 1.4 de ce chapitre, je m'étendrai un peu plus sur les destructeurs virtuels, retenez dès à présent qu'un destructeur d'une classe de base devrait toujours être déclaré virtuel pour autant qu'une méthode de la classe de base soit redéfinie dans une classe dérivée.

   Voyons le résultat dans la console : 

 

 

    Exercice :

   Réécrivez le programme précédent en ajoutant les pointeurs vers les objets dans un tableau que vous déclarerez au début de la fonction main().
   Vous afficherez les fonctions Se_Presenter() à l'aide des indices du tableau.

 


   1.2 – Passage d'un pointeur, d'une référence ou d'un objet en argument d'une fonction non membre

   Dans l'exemple précédent nous avons implémenté une fonction Presentation() non membre d'une classe et à laquelle nous avons passé une référence sur un objet personnage en argument.

   Nous pouvions également passer un pointeur ou un objet à une fonction, celle-ci aurait affiché de la même manière la fonction Se_Presenter() impliquant un objet Personnage.

   Nous allons modifier le programme 158HeritageSimple_11 en ajoutant ces fonctions mais en ne réécrivant que ce qui est nécessaire à la présentation. wink


   Projet 159HeritageSimple_12  

 

//Déclaration des fonctions de présentation
void PresentationPtr(Personnage *p) //On passe un pointeur sur un personnage en argument
{
p->Se_Presenter();
}
 
void PresentationRef(Personnage &p) //On passe une référence sur un personnage en argument
{
p.Se_Presenter();
}
 
void PresentationObj(Personnage p) //On passe un objet personnage en argument
{
p.Se_Presenter();
}
 
int main()
{
 
Personnage unPersonnage;
Personnage *ptr = nullptr;
char choix('1');
 
std::cout << std::endl;
 
while (choix == '1' || choix == '2' || choix == '3' || choix == '4' || choix == '5')
{
std::cout << "(1) Personnage" << std::endl;
std::cout << "(2) Guerrier" << std::endl;
std::cout << "(3) Archer" << std::endl;
std::cout << "(4) Druide" << std::endl;
std::cout << "(5) Affiche les fonctions non membres" << std::endl;
std::cout << "Pressez un autre caractere pour quitter" << std::endl <<
std::endl;
std::cout << "Votre choix : ";
std::cin >> choix;
 
switch (choix)
{
 
case '1':
ptr = new Personnage;
ptr->Se_Presenter();
std::cout << std::endl;
break;
case '2':
ptr = new Guerrier;
ptr->Se_Presenter();
std::cout << std::endl;
break;
case '3':
ptr = new Archer;
ptr->Se_Presenter();
std::cout << std::endl;
break;
case '4':
ptr = new Druide;
ptr->Se_Presenter();
std::cout << std::endl;
break;
 
case '5':
std::cout << "Fonction non membre a laquelle on passe un pointeur" <<
std::endl;
PresentationPtr(&unPersonnage);
std::cout << std::endl;
 
std::cout << "Fonction non membre a laquelle on passe une reference" <<
std::endl;
PresentationRef(unPersonnage);
std::cout << std::endl;
 
std::cout << "Fonction non membre a laquelle on passe un objet" <<
std::endl;
PresentationObj(unPersonnage);
std::cout << std::endl;
break;
 
default:
ptr = new Personnage;
break;
}
 
std::cout << std::endl;
}
 
delete ptr;
return 0;
}  

 

      La modification est facilement compréhensible. Nous implémentons trois fonctions non membres PresentationPtr(), PresentationRef() et PresentationObj() auxquelles nous passons respectivement un pointeur, une référence et un objet en argument.

   La fonction main() est quelque peu modifiée. Les choix 1, 2, 3 et 4 appelleront directement les fonctions virtuelles Se_Presenter() qui afficheront chacune les présentations des objets Personnage, Guerrier, Archer et Druide. Le choix numéro 5 appellera, quant à lui, les trois fonctions non membres, chacune d'entre-elles affichant la présentation d'un objet Personnage.

   Et ceci nous donne dans la console :

 

 

 

   1.3 – Obligation et interdiction de surcharge de méthodes virtuelles


   La norme C++ 11 a introduit les mots-clés ''final'' et ''override'' permettant chacun l'interdiction ou l'obligation de surcharger une méthode virtuelle.

   Si le mot réservé final est placé à la suite de la déclaration ou de la définition d'une méthode virtuelle, il est alors impossible de surcharger cette méthode dans une classe fille.

   Inversement, le mot-clé override permet de définir une méthode surchargée dans une classe dérivée à condition que la signature de la méthode de la classe dérivée soit identique à la signature de la méthode virtuelle de la classe de base (nous avons vu que la signature d'une fonction était le nombre de paramètres passés à cette fonction ainsi que leur type wink).

   Nous allons voir l'implication de l'ajout de ces identifiants en réécrivant les classes du projet 158HeritageSimple_11

 

//Déclaration de la classe Personnage
class Personnage
{
public:
 
//Constructeur et destructeur
Personnage() {};
virtual ~Personnage() {};
 
virtual void Se_Presenter() const
{
std::cout << "Je suis un personnage, le chef du village" << std::endl;
}
 
virtual void Pas_de_surcharge() const final
{
std::cout << "Je suis l'ancetre de la tribu" << std::endl;
}
 
 
protected:
 
};
 
 
//Déclaration de la classe Guerrier
class Guerrier : public Personnage
{
public:
 
virtual void Se_Presenter() const override
{
std::cout << "Je suis un guerrier, defenseur du village" << std::endl;
}
 
//Cette méthode ne peut pas être surchargée dans une classe dérivée
/*
virtual void Pas_de_surcharge() const
{
std::cout << "Je suis l'ancetre de la tribu" << std::endl;
}
*/
 
protected:
 
};
 
 
//Déclaration de la classe Archer
class Archer : public Personnage
{
public:
 
virtual void Se_Presenter() const override
{
std::cout << "Je suis un archer, defenseur de la tour" << std::endl;
}
 
protected:
 
};
 
 
//Déclaration de la classe Druide
class Druide : public Personnage
{
public:
 
virtual void Se_Presenter() const override
{
std::cout << "Je suis un druide, guerisseur du village" << std::endl;
}
 
protected:
 
}; 

 

   Dans la classe Personnage nous avons implémenté une seconde méthode virtuelle dénommée Pas_de_surcharge() et à laquelle nous avons appliqué l'identifiant 'final'. Nous décidons ainsi que cette méthode ne pourra pas être surchargée dans une classe dérivée.

   Nous avons cependant redéfini cette méthode dans la classe Guerrier mais le compilateur s'est gentiment moqué en disant qu'il ne pouvait pas surcharger une méthode déclarée 'final'. C'est pourquoi cette méthode a été placée sous forme de commentaires dans la classe Guerrier.

   Dans les classes dérivées, nous avons appliqué l'identifiant 'override' à chacune des méthodes virtuelles Se_Presenter(), précisant ainsi que ce sont des méthodes redéfinies. Ceci est autorisé car les méthodes redéfinies possèdent la même signature que la méthode de la classe mère. En effet, en supposant que la méthode virtuelle Se_Presenter() de la classe Guerrier ait par exemple un nom en paramètre, soit :

    

virtual void Se_Presenter(string nom) const
{
std::cout << "Je suis un guerrier, defenseur du village" << std::endl;
} 

 

     Dans ce cas, il aurait été interdit d'ajouter l'identifiant 'override' à la suite de la déclaration de la méthode redéfinie dans la classe Guerrier car il y aurait eu interdiction de surcharge de la méthode de la classe de base, la signature des deux méthodes étant différente. Ajouter l'identifiant override à la suite d'une méthode redéfinie dans une classe dérivée peut ainsi nous permettre d'éviter des erreurs et s'assurer qu'il s'agit bien d'une méthode redéfinie.

 

   1.4 – Destructeurs virtuels

   Si vous vous reportez au projet 158HeritageSimple_11 vous verrez que nous avions déclaré notre destructeur 'virtual'.

   Nous allons à présent montrer pourquoi et dans quel cas un destructeur doit être déclaré virtuel.

   Nous présenterons tout d'abord un exemple qui crée dans le tas un objet d'une classe dérivée à partir d'une classe de base sans implémenter un destructeur virtuel. Nous suivrons ensuite les appels de constructeurs/destructeurs dans cet exemple.

   Projet 160HeritageSimple_13 

 

//Projet 160HeritageSimple_13
//Création d'un objet d'une classe dérivée à partir d'une classe de base
//Implémentation d'un destructeur NON virtuel
//Fichier : HeritageSimple_1.cpp
#include <iostream>
#include <conio.h>
 
class Personnage
{
public:
Personnage() { std::cout << "Constructeur de Personnage" << std::endl; }
~Personnage() { std::cout << "Destructeur de Personnage"; }
 
virtual void Se_Presenter() const
{
std::cout << "Je suis un personnage, le chef du village" << std::endl;
}
};
 
 
class Guerrier : public Personnage
{
public:
Guerrier() { std::cout << "Constructeur de Guerrier" << std::endl; }
~Guerrier() { std::cout << "Destructeur de Guerrier" << std::endl; }
 
virtual void Se_Presenter() const override
{
std::cout << "Je suis un guerrier, defenseur du village" << std::endl;
}
};
 
 
 
int main()
{
std::cout << std::endl;
 
//Création des objets Personnage et Guerrier
Personnage *unGuerrier = new Guerrier;
 
unGuerrier->Se_Presenter();
 
delete unGuerrier;
std::cout << std::endl;
_getch();
 
return 0;
} 

 

      Allez, on montre d'abord les résultats dans la console et on explique par la suite. laughing

 

 

   Dans cet exemple, tout ce qui nous intéresse se trouve dans la fonction main(). En effet, au début de celle-ci, nous créons un objet unGuerrier à partir de la classe de base Personnage. Ceci est tout à fait autorisé, un guerrier héritant publiquement de la classe Personnage.

   Les deux premières lignes de la console nous montrent qu'un guerrier a bien été créé, le constructeur de Personnage étant appelé en premier et le constructeur de Guerrier en second.

   Par la suite, l'objet unGuerrier appelle la méthode Se_Presenter() qui affiche la présentation d'un guerrier à la troisième ligne de notre console.

   Pour terminer, nous voulons détruire l'objet à l'aide de l'instruction 'delete' mais nous nous apercevons à la dernière ligne de la console que c'est le destructeur de personnage qui a été appelé ? surprised

   En effet, unGuerrier a bien été construit à partir de la classe Personnage, donc c'est le destructeur de celle-ci qui est appelé. Il s'ensuit que l'objet unGuerrier n'est pas détruit, et que dès cet instant nous risquons des fuites de mémoire ainsi que l'une ou l'autre catastrophe lors d'un projet plus conséquent. money-mouth

   Le remède à apporter à cet exemple est tout simple. Nous devons déclarer 'virtual' le destructeur de la classe Personnage. En effet, si le destructeur est virtuel, le destructeur de la classe dérivée sera appelé en premier et celui-ci appellera ensuite le destructeur de la classe de base. L'objet sera ainsi correctement détruit.

   En déclarant 'virtual' notre destructeur de la classe Personnage, nous obtenons alors ce nouvel affichage de la console :

 

 


     Ce qui correspond très exactement à ce que nous venons de dire ci-dessus. wink

   Nous rappellerons ici ce qui a été dit dans le paragraphe 1.1, à savoir qu'un destructeur d'une classe de base devrait toujours être déclaré virtuel pour autant qu'une méthode de la classe de base soit redéfinie dans une classe dérivée.


   1.5 – Limites de l'héritage simple

   Les exemples que nous avons vus jusqu'à présent utilisaient tous l'héritage simple. Sous ce type d'héritage, une classe ne peut dériver que d'une seule classe mère. En effet, un guerrier, un archer ou un shaman hérite d'un personnage mais il se pourrait très bien que nous ayons besoin d'un objet devant hériter de caractéristiques d'objets de deux ou plusieurs classes différentes.

   Prenons par exemple le cas d'un cheval et d'un centaure. Notre cheval est un animal pouvant être dompté et dressé ou parcourir la campagne tout en galopant. Le centaure quant-à-lui est une espèce d'animal fantastique mi-homme mi-cheval de la mythologie grecque pouvant parler et combattre à l'aide d'un arc. De même un minotaure pourrait être un objet dérivant d'une classe Guerrier et d'une classe Taureau.

   Nous pouvons concevoir que notre centaure, non entraîné et non équipé, dérivant d'une classe Cheval, ne puisse avoir accès à une arme de traits sans avoir reçu un entraînement approprié de la part d'un archer.

   Avec l'héritage simple, il est donc difficile de permettre à un centaure de combattre s'il ne sait pas se servir d'un arc !? surprised

   Voyons donc les limites de cet exemple qui utilise l'héritage simple. Nous ferons à cet effet dériver la classe Centaure de la classe Cheval.


   Projet 161HeritageSimple_14      

 

//Projet 161HeritageSimple_14
//Si les centaures étaient armés...
//Fichier : HeritageSimple_1.cpp
 
#include <iostream>
 
using namespace std;
using uint = unsigned int;
 
class Cheval
{
public:
 
virtual void Galoper() const { cout << "Un cheval galope !" << endl; }
 
virtual void Parler() const
{
cout << "Les chevaux ne savent pas parler !" << endl;
}
 
virtual void Tir_a_l_arc() const
{
cout << "Les chevaux ne tirent pas a l'arc !" << endl;
}
 
 
protected:
 
uint _qte_Vie;
 
};
 
 
class Centaure : public Cheval
{
public:
 
virtual void Galoper() const override { cout << "Je galope !" << endl; }
 
virtual void Parler() const override
{
cout << "Je vais vous pietiner !" << endl;
}
 
virtual void Tir_a_l_arc() const override
{
cout << "Je n'ai pas recu d'entrainement et ne suis pas arme !" << endl;
}
 
protected:
 
};
 
 
int main()
{
 
Cheval *ptr = nullptr;
 
int i;
char choix('1');
 
cout << endl;
 
while (choix == '1' || choix == '2')
{
std::cout << "(1) Cheval" << std::endl;
std::cout << "(2) Centaure" << std::endl;
std::cout << "Pressez un autre caractere pour quitter" << std::endl << std::endl;
std::cout << "Votre choix : ";
std::cin >> choix;
 
switch (choix)
{
 
case '1':
ptr = new Cheval;
ptr->Galoper();
ptr->Parler();
ptr->Tir_a_l_arc();
std::cout << std::endl;
break;
 
case '2':
ptr = new Centaure;
ptr->Galoper();
ptr->Tir_a_l_arc();
ptr->Parler();
std::cout << std::endl;
break;
 
default:
ptr = new Cheval;
break;
}
}
 
delete ptr;
return 0;
} 

 

  Voila, par héritage simple notre centaure n'hérite que d'une seule classe, ici la classe Cheval. Cet héritage est plutôt minimaliste car comme le cheval, le centaure ne peut que galoper. En effet, dans la méthode virtuelle Tir_a_l_arc() de la classe Centaure nous avons signalé que celui-ci n'avait acquis aucune compétence au niveau du tir à l'arc. En d'autres termes, notre centaure aurait dû hériter d'une méthode Tir_a_l_arc() provenant d'une classe Archer par exemple. En fait, notre animal fantastique mi-homme mi-cheval devrait hériter de deux classes de base différentes, les classes Archer et Cheval.

   Notez que nous aurions pu, dans la classe Centaure, définir la méthode tir_a_l_arc() de la classe Archer du projet 150HeritageSimple_3 du chapitre 25, soit : 

 

void tir_a_l_Arc(Cheval &autre) const
{
autre.recevoirDegats(7);
} 

 

     Mais que ceci n'aurait pas eu beaucoup de sens, le centaure ne pouvant tirer que sur un cheval dans cet exemple. wink

   Bien, revenons à notre projet 161HeritageSimple14. La fonction main() ne doit pas vous poser de problèmes, nous avons déjà expliqué son implémentation dans les deux exemples précédents (158HeritageSimple_11 et 159HeritageSimple_12).

   Les résultats de notre projet dans la console :

 

   

  

  2.0 – La solution à l'héritage simple : l'héritage multiple

     2.1 – Dériver une classe fille de deux classes de base

   L'héritage multiple permet donc à une classe dérivée d'hériter des caractéristiques propres à deux ou plusieurs classes de base.

   D'une manière générale, la syntaxe de déclaration d'une classe dérivant de deux classes de base sera :

class classeDerivee : typeAcces classeDeBase1, typeAcces classeDeBase2

   Le nom des deux classes mères étant séparé par une virgule.

   Nous allons maintenant dans un nouvel exemple, remplacer notre centaure par un archer de cavalerie. Bien, nous voulons que ce nouveau sujet apprenne une spécialisation de tir à l'arc à l'aide d'un arc composite par exemple. Rien de plus facile : en plus que de faire dériver la classe Archer_de_Cavalerie d'une classe Cheval (dont nous remplacerons le nom par 'Eclaireur', ce qui est plus parlant dans un jeu de rôle wink), nous la ferons également dériver d'une classe Archer, l'apprentissage de notre nouvel ami sera ainsi complet. laughing

 

 

   Nous voyons ceci dans l'exemple suivant :

      Projet 162HeritageMultiple_1

 

//Projet 162HeritageMultiple_1
//Une classe fille qui dérive de deux classes de base
//Fichier : HeritageMultiple_1.cpp
 
#include <iostream>
#include <conio.h>
 
using namespace std;
using uint = unsigned int;
 
class Eclaireur
{
public:
 
//constructeur et destructeur
Eclaireur() { cout << "Constructeur d'Eclaireur..." << endl; }
virtual ~Eclaireur() { cout << "Destructeur d'Eclaireur..." << endl; }
 
virtual void Se_Presenter() const
{
cout << "Je suis un eclaireur a cheval, j'emmene la troupe !" << endl;
}
 
void Galoper() { cout << "Je galope !" << endl; }
 
protected:
 
uint _qte_Vie;
 
};
 
 
class Archer
{
public:
 
//constructeur et destructeur
Archer() { cout << "Constructeur d'Archer..." << endl; }
virtual ~Archer() { cout << "Destructeur d'Archer..." << endl; }
 
virtual void Se_Presenter() const
{
cout << "Je suis un archer, je defends la cite !" << endl;
}
 
void Tir_a_l_arc() const
{
cout << "Je sais tirer avec un arc court ou long !" << endl;
}
 
 
protected:
 
 
};
 
 
class Archer_de_cavalerie : public Eclaireur, public Archer
{
public:
 
//constructeur et destructeur
Archer_de_cavalerie() { cout << "Constructeur d'Archer de cavalerie..." << endl; }
virtual ~Archer_de_cavalerie() { cout << "Destructeur d'Archer de cavalerie..." << endl; }
 
virtual void Se_Presenter() const override
{
cout << "Je suis un archer a cheval, je me deplace rapidement !" << endl;
}
 
//Cette fonction est inutile, Archer_de_cavalerie en hérite de Eclaireur !
//void Galoper() { cout << "Je galope !" << endl; }
 
void Tir_a_l_arc2()
{
Tir_a_l_arc(); 
cout << "J'utilise un arc composite" << endl;
}
 
protected:
 
};
 
int main()
{
 
Eclaireur *ptr = nullptr;
Archer *ptr2 = nullptr;
Archer_de_cavalerie *ptr3 = nullptr;
 
int i;
char choix('1');
 
cout << endl;
 
while (choix == '1' || choix == '2' || choix == '3')
{
cout << "(1) Eclaireur" << endl;
cout << "(2) Archer" << endl;
cout << "(3) Archer de cavalerie" << endl;
cout << "Pressez un autre caractere pour quitter" << endl << endl;
cout << "Votre choix : ";
cin >> choix;
 
switch (choix)
{
 
case '1':
ptr = new Eclaireur;
ptr->Se_Presenter();
ptr->Galoper();
cout << endl;
break;
 
case '2':
ptr2 = new Archer;
ptr2->Se_Presenter();
ptr2->Tir_a_l_arc();
cout << endl;
break;
 
case '3':
ptr3 = new Archer_de_cavalerie;
ptr3->Se_Presenter();
ptr3->Tir_a_l_arc2();
ptr3->Galoper();
cout << endl;
break;
 
default:
ptr3 = new Archer_de_cavalerie;
break;
}
 
}
 
ptr = nullptr;
ptr2 = nullptr;
 
delete ptr3;
 
_getch();
 
return 0;
} 

 

     Dans chacune de nos trois classes, nous initialisons un constructeur et un destructeur qui implémentent les appels de la construction ou de la destruction d'un objet.

   La classe Eclaireur implémente également une méthode virtuelle Se_Presenter() ainsi qu'une méthode non virtuelle Galoper(). Celle-ci sera accessible à un objet Archer_de_cavalerie par héritage.

  Dans la classe Archer, nous ajoutons une méthode supplémentaire Tir_a_l_arc() dont un objet Archer_de_cavalerie pourra également hériter.

   La classe Archer_de_cavalerie hérite des classes Eclaireur et Archer. Dans cette classe, la méthode Se_Presenter() est virtuelle par héritage mais nous le signalons quand-même, nous en avons expliqué les raisons dans le précédent chapitre. wink

   Nous implémentons également une méthode Tir_a_l_arc2() qui a ceci d'intéressant : par héritage, notre archer de cavalerie a appris à se servir d'un arc court ou d'un arc long. Un objet Archer_de_cavalerie peut donc appeler une méthode Tir_a_l_arc(). Nous le faisons à l'intérieur de la méthode Tir_a_l_arc2(). Notre archer de cavalerie a en outre appris une compétence supplémentaire, celle de se servir d'un arc composite que nous ajoutons comme deuxième ligne dans la méthode.

   Voilà, grâce à l'héritage multiple, un objet Archer_de_cavalerie peut donc accéder à la méthode Galoper() de la classe Eclaireur, appeler la méthode virtuelle Se_Presenter(), accéder à la méthode Tir_a_l_arc() de la classe Archer ou appeler la méthode Tir_a_l_arc2() de sa propre classe, s'il veut utiliser ses nouvelles compétences.

   Cet exemple a été simplifié au maximum afin que vous puissiez bien comprendre le mécanisme d'héritage multiple. Bien entendu, tout ceci peut-être complexifié à souhait. laughing

   Et tout ceci nous donne dans la console :

 

 

 

   Exercice 2 (un petit challenge)

   Réécrivez ce programme en utilisant des références à la place de pointeurs. Si besoin en est, vous pouvez relire le paragraphe 1.3 du chapitre 9 concernant la ''Création et destruction de références dans le tas''.

 

 

   3. Corrigé de l'exercice du chapitre 26

   Pour cet exercice on demandait de réécrire l'exemple 154HeritageSimple_7 du chapitre 26 en y ajoutant les méthodes membres avancer(), reculer() et crier() du projet 148HeritageSimple_1 du chapitre 25 qu'il fallait placer dans la classe Personnage. Vous deviez redéfinir ces méthodes dans la classe Sportif et passer une variable nb_pas en paramètre aux méthodes avancer() et reculer(). Vous deviez également faire pousser un cri différent à un personnage et à un sportif.

   Le code :

      Fichier Chap26Ex_1.cpp 

 

//projet Chap26Exercice_1
//Fichier : Chap26Ex_1.cpp
//Masquage d'une méthode de classe de base
 
#ifndef CHAP26_EX_1
#define CHAP26_EX_1
 
#include <iostream>
 
using namespace std;
 
class Personnage
{
public:
 
void Sprinter() const
{
cout << "Un personnage realise une course de 100 metres" << endl;
}
 
void Sprinter(int distance) const
{
cout << "Un personnage realise une course de " << distance << " metres";
cout << endl;
}
 
void Avancer()
{
cout << "Un personnage avance d'un pas" << endl;
}
 
void Avancer(int nb_pas)
{
cout << "Un personnage avance de " << nb_pas << " pas" << endl;
}
 
void Reculer()
{
cout << "Un personnage recule d'un pas" << endl;
}
 
void Reculer(int nb_pas)
{
cout << "Un personnage recule de " << nb_pas << " pas" << endl;
}
 
void Crier()
{
cout << "Un personnage crie 'Whooo'" << endl;
}
 
 
protected:
 
};
 
 
class Sportif : public Personnage
{
public:
 
void Sprinter() const
{
std::cout << "Un sportif realise une course de 800 metres" << std::endl;
}
 
void Sprinter(int distance) const
{
std::cout << "Un Sportif realise une course de " << distance << " metres" << std::endl;
}
 
void Avancer()
{
cout << "Un sportif avance d'un pas" << endl;
}
 
void Avancer(int nb_pas)
{
cout << "Un sportif avance de " << nb_pas << " pas" << endl;
}
 
void Reculer()
{
cout << "Un sportif recule d'un pas" << endl;
}
 
void Reculer(int nb_pas)
{
cout << "Un sportif recule de " << nb_pas << " pas" << endl;
}
 
void Crier()
{
cout << "Un sportif crie 'Vive le sport !'" << endl;
}
 
};
 
#endif
 
 
int main()
{
Personnage unPersonnage;
Sportif unSportif;
 
cout << endl;
unPersonnage.Sprinter();
unPersonnage.Sprinter(400);
unPersonnage.Avancer();
unPersonnage.Avancer(3);
 
unPersonnage.Reculer();
 
unPersonnage.Reculer(2);
unPersonnage.Crier();
cout << endl;
 
unSportif.Sprinter();
unSportif.Sprinter(1000);
unSportif.Avancer();
unSportif.Avancer(5);
unSportif.Reculer();
unSportif.Reculer(3);
unSportif.Crier();
 
cin.get();
return 0;
} 

 

     Et bien je pense qu'il n'y a pas besoin ici de commenter cet exercice, il était vraiment facile. laughing Je vous rappelle néanmoins de ne pas oublier de redéfinir toutes vos méthodes d'une classe de base dans une classe dérivée si une (même une seule wink) d'entre-elles a été redéfinie dans la classe dérivée car vous ne pourriez pas avoir accès aux autres méthodes dans la classe dérivée.

   Et ceci nous donne comme résultat dans la console :

 

 

   Voilà, c'est tout en ce qui concerne ce chapitre. cool

   Les corrigés des exercices de ce chapitre seront présentés à la fin du chapitre 28.

      @ bientôt pour le chapitre 28 – Polymorphisme (2).                                                                     

            Gondulzak.  angel
 
 
 

Connexion

CoalaWeb Traffic

Today236
Yesterday297
This week1033
This month4707
Total1743914

26/04/24