Classe abstraite et fonction virtuelle pure
Classe abstraite et fonction virtuelle pure
Dans l’exemple de code 7.3(fonctions virtuelles), la classe Noeud a ceci de particulier qu’elle représente uniquement un concept. Elle a été définie pour isoler les caractéristiques générales des différents types de nœuds (ListeChainee, NoeudInterne, et Queue) et le programme ne créera pas d’objets de ce type, seulement des objets basés sur ce type. On dit qu’il s’agit d’une classe abstraite ou d’un type de donnée abstrait (vous pourrez entendre parler d’ADT pour Abstract Data Typexe "ADT (Abstract Data Type)").En C++, vous créez une classe abstraite en déclarant au moins une fonction virtuelle pure, c’est-à-dire en initialisant une fonction virtuelle à zéro (voir Afficher() et Insérer() dans le code 7.4). L’idée est d’imposer ainsi aux concepteurs des classes dérivées de redéfinir et de fournir une implémentation concrète de la fonction.
Voici le code de cette classe :
Code 7.4 : la classe abstraite Noeud
Class Noeud { private: public: Noeud(){} virtual ~Noeud(){} //les 2 fonctions virtuelles pures virtual Noeud * Inserer(Donnees *un_objet) = 0; virtual void Afficher() = 0; };
Pour améliorer la liste chaînée créée au code 7.3, nous pourrions transformer la classe Donnees en type abstrait pour offrir à l’utilisateur la possibilité de stocker différents types de données dans la liste chaînée et pour traiter ces différents types de Donnees de façon polymorphique. En version décodée, la liste chaînée va simplement manipuler des types Donnees sans avoir besoin de connaître les caractéristiques de chaque type qui en sera dérivé.
Le code 7.5 présente uniquement la classe Donnees modifiée, les deux nouvelles classes qui en sont dérivées et la nouvelle fonction main(). Pour compléter ce programme, ajoutez les déclarations et définitions des classes Noeud, ListeChainee, NoeudInterne, et Queue.
Code 7.5 : une liste chaînée plus « polymorphique »xe "liste chaînée:polymorphique"
/*******************Classe Donnees**********************/ class Donnees //classe abstraite { public: Donnees(int val):valeur_objet(val){} virtual ~Donnees(){} int Comparer(const Donnees &); virtual void Afficher() = 0; //fonction virtuelle pure protected: int valeur_objet; }; /****Définition de la fonction Comparer de Donnees******/ int Donnees::Comparer(const Donnees & autre_objet) { if (valeur_objet < autre_objet.valeur_objet) return plus_petit; if (valeur_objet > autre_objet.valeur_objet) return plus_grand; else return egal; } /****************Classe DonneeEntiere******************/ class DonneeEntiere : public Donnees { public: //le constructeur initialise val : DonneeEntiere(int val) : Donnees(val) {} virtual ~DonneeEntiere() {} virtual void Afficher(); private: }; /*Implémentation de la fonction virtuelle pure Afficher*/ void DonneeEntiere::Afficher() { cout << "La valeur de l’entier est " << valeur_objet << endl; } /******************Classe DonneeGraphique*****************/ class DonneeGraphique : public Donnees { public: DonneeGraphique(int val) : Donnees(val) {} virtual ~DonneeGraphique() {} virtual void Afficher(); private: }; /*Implémentation de la fonction virtuelle pure Afficher*/ void DonneeGraphique::Afficher() { cout << "(" << valeur_objet << " étoiles): ";; for ( int i = 0; i < valeur_objet; i++) cout << "*"; cout << endl; } /****************Fonction principale****************/ int main() { Donnees * p_donnees; //on crée un objet Donnees de // pointeur p_donnees int val; //valeur à stocker dans la liste int type_donnees; //type de la valeur val ListeChainee lc; //on crée un objet listechainee for (;;) //tant que l’utilisateur de saisi pas 0 { cout << "[1] Entier, [2] Etoiles, [0] Quitter: "; cin >> type_donnees; //on lit la valeur saisie if (!type_donnees) //si 0 break; //on termine switch(type_donnees) { case 1: //si 1 cout << "Quelle valeur voulez-vous ajouter à la liste ? "; cin >> val; //on lit la valeur entière p_donnees = new DonneeEntiere(val); break; case 2: //si 2 cout << "Quelle quantité d’étoiles voulez-vous ajouter à la liste ? "; cin >> val; //on lit toujours une valeur entière mais //elle représente cette fois une quantité p_donnees = new DonneeGraphique(val); } lc.Inserer(p_donnees); //on stocke dans la liste } cout << "\n\n"; lc.Afficher(); //on affiche les valeurs stockées dans cout << "\n\n"; //la liste par ordre croissant return 0; }
En exécutant ce programme, on obtient le résultat suivant :
[1] Entier, [2] Etoiles, [0] Quitter: 1 Quelle valeur voulez-vous ajouter à la liste ? 5 [1] Entier, [2] Etoiles, [0] Quitter: 2 Quelle quantité d’étoiles voulez-vous ajouter à la liste ? 10 [1] Entier, [2] Etoiles, [0] Quitter: 1 Quelle valeur voulez-vous ajouter à la liste ? 3 [1] Entier, [2] Etoiles, [0] Quitter: 2 Quelle quantité d’étoiles voulez-vous ajouter à la liste ? 2 [1] Entier, [2] Etoiles, [0] Quitter: 0 <2 étoiles>: ** La valeur de l’entier est 3 La valeur de l’entier est 5 <10 étoiles>: **********
Substitution d’une fonction virtuelle pure
Les classes concrètes doivent absolument définir toutes les fonctions virtuelles pures, mais la classe abstraite a la possibilité d’en définir une. Notre classe Donnees pourrait fournir l’implémentation de la fonction Afficher(), par exemple :Code 7.6 : implémentation d’une fonction virtuelle pure dans la classe de base
1 :class Donnees 2 :{ 3 :public: 4 : Donnees(int val):valeur_objet(val){} 5 : virtual ~Donnees(){} //destructeur virtuel 6 : int Comparer(const Donnees &); 7 : virtual void Afficher() = 0; //fonction virtuelle pure 8 :protected: 9 : int valeur_objet; 10:}; 11://implémentation d’un affichage de base : 12:void Donnees::Afficher() 13:{ 14: cout << “\nVoici la valeur de vos données: \n”; 15:}
Les classes dérivées appellent cette fonction via l’opérateur de résolution de portée ::. Cela permet à la classe abstraite d’offrir une fonction de base tout en imposant aux fonctions dérivées l’implémentation d’une fonction plus spécialisée :
void DonneeGraphique::Afficher() { Donnees::Afficher(); // appelée via l’opérateur de portée cout << “(“ << valeur_objet << “): “; for ( int i = 0; i < valeur_objet; i++) cout << “*”; cout << endl; }
Le texte original de cette fiche pratique est extrait de
«Tout sur le C++» (Christine EBERHARDT, Collection
CommentCaMarche.net, Dunod, 2009)