© D. Mathieu
mathieu@romarin.univ-aix.fr
I.U.T.d'Aix en Provence - Département Informatique
Créé le 08/02/2000 -
Dernière mise à jour : 04/02/2001
Remarques préliminaires :
Dans l'espace de noms nsSdD (namespace Structures de Données), créer une classe CElement (fichiers CElement.h et CElement.hxx) possédant les deux données membres privées et les fonctions membres publiques (en inline) suivantes (figure 1) :
figure 1 : élément d'une liste simplement chaînée
A cause de la présence d'un pointeur comme donnée membre, interdire le constructeur par copie et l'affectation.
Dans le fichier exo_01.cxx, écrire la fonction Exo_01() qui effectue les opérations suivantes :
Nous verrons ultérieurement qu'il est possible de donner d'autres implémentations d'une liste simplement chaînée.
Corrigés : exo_01.cxx - CElement.h - CElement.hxx - Makefile_01
Recopier les fichiers CElement.h, CElement.hxx, Makefile_01 et exo_01.cxx dans les fichiers CElemGen.h, CElemGen.hxx, Makefile_02 et exo_02.cxx. Transformer la fonction Exo_01() en Exo_02() et la classe CElement en classe générique CElemGen de paramètre de généricité T (figure 3). Ajouter à la fonction Exo_02() une instruction typedef permettant, en instanciant la classe générique CElemGen par le type int, d'utiliser le reste de la fonction sans aucune autre modification.
Modifier le destructeur de CElemGen pour qu'il ne détruise plus la totalité de la liste (cela revient à avoir un destructeur vide).
Mettre à jour le fichier Makefile_02 (ajout des variables de dépendance CELEMGEN_H et CELEMGEN_HXX). Afin de pouvoir utiliser le dernier fichier Makefile??? pour compiler aussi les exercices précédents, on ne supprimera pas les éléments qui ne sont plus utiles pour l'exercice courant.
Compiler et tester.
Remarques :
Corrigés : exo_02.cxx - CElemGen.h - CElemGen.hxx - Makefile_02
Dans cet exercice, l'Info est une donnée de la classe CDuree. Recopier les fichiers exo_02.cxx et Makefile_02 dans exo_03.cxx et Makefile_03. Modifier la fonction Exo_02() en Exo_03() pour créer une liste de 10 durées, les afficher successivement au moyen de l'injecteur et détruire la liste.
Mettre à jour le fichier Makefile_03 (ajout des variables de dépendance CDUREE_H et CDUREE_HXX, et compilation de CDuree.cxx). Compiler et tester.
Corrigés : exo_03.cxx - Makefile_03
La classe CElement développée dans le premier paragraphe présente deux parties très distinctes : les données et fonctions membres portant sur la partie chaînage (m_Suivant et GetSuivant()) et celles portant sur l'information (m_Info et GetInfo()).
Par ailleurs, l'accès aux fonctions membres de la partie générique Info de chaque élément nécessite deux appels, ce qui alourdit l'écriture, comme le montre l'exemple ci-dessous :
typedef CElemGen <CDuree> CElement;
CElement * Ptr; // .. Ptr->GetInfo().GetDuree(); |
Une solution fait disparaître cet inconvénient (figure 5) : développer une classe CLinkSimple possédant toutes les fonctionnalités nécessaires à la gestion du chaînage (m_Suivant et GetSuivant()), puis lui ajouter par dérivation celles portant sur l'information (une durée par exemple).
Dans les fichiers CLinkSimple.h et CLinkSimple.hxx, écrire la classe CLinkSimple correspondante. La classe CLinkSimple doit logiquement faire partie de l'espace de noms nsSdD puisqu'elle est faite pour créer des listes chaînées.
Puis recopier les fichiers CDuree.h, CDuree.hxx et CDuree.cxx dans les fichiers CDureeLink.h, CDureeLink et CDureeLink.cxx. Plusieurs modifications doivent être effectuées :
Recopier le fichier exo_03.cxx dans exo_04.cxx. Transformer la fonction Exo_03() en Exo_04() en utilisant la nouvelle classe CDureeLink.
Remarque :
Un pointeur sur un objet d'une classe fille pointe toujours implicitement sur un objet de la classe mère (un objet de la classe fille est un objet de la classe mère, éventuellement complété : un chat est un mammifère). L'inverse n'est pas vrai (un mammifère n'est pas forcément un chat). La fonction GetSuivant() renvoyant un pointeur vers un CLinkSimple, la conversion en un pointeur vers la classe fille doit être explicitement demandée. Il s'agit d'un transtypage (cast) vers le bas, ou down-cast :
CDureeLink * Ptr = ...
// ..
Ptr = static_cast <CDureeLink *> (Ptr->GetSuivant());Cette conversion est effectuée au moment de la compilation, contrairement à la conversion dynamic_cast, qui ne peut être effectué qu'à l'exécution (ligature dynamique).
Recopier le fichier Makefile_03 dans le fichier Makefile_04 (ajout des variables de dépendance CLINKSIMPLE_H, CLINKSIMPLE_HXX, CDUREELINK_H et CDUREELINK_HXX, et compilation de CDureeLink.cxx). Compiler et tester.
Pour éviter ce transtypage, il faut surcharger, dans la classe fille, toutes les fonctions membres publiques de la classe mère dont la valeur de retour nécessiterait un transtypage. Ici, il s'agit seulement de la fonction GetSuivant(). Nous adopterons cette solution chaque fois que cela sera nécessaire.
Dans la classe CDureeLink, sucharger la fonction GetSuivant(). Dans la fonction Exo_03(), ajouter une boucle d'affichage sans transtypage. Compiler et tester.
Remarque :
Corrigés : exo_04.cxx - CLinkSimple.h - CLinkSimple.hxx - CDureeLink.h - CDureeLink.hxx - CDureeLink.cxx - Makefile_04
Il n'est pas toujours possible de dériver la classe CLinkSimple en une autre classe, comme nous l'avons fait avec la classe CDureeLink, par exemple si le corps de la classe CDuree est livré sous forme compilée. De plus, un important travail de réécriture et d'adaptation, source potentielle d'erreurs, est nécessaire.
Conceptuellement, la solution proposée n'est pas non plus satisfaisante : même si, techniquement, on peut faire dériver une durée d'un lien, il n'y a aucune raison de considérer qu'une durée est d'abord et avant tout un lien, enrichi d'informations complémentaires.
Enfin, l'impossibilité de créer des durées en dehors d'une liste rend la solution inefficace.
La double dérivation que le C++ est le seul à autoriser, offre une alternative intéressante (figure 6).
Dans les fichiers CDureeAndLink_05.h et CDureeAndLink_05.hxx, dériver la classe CDureeAndLink (espace de noms nsWorking), des classes CLinkSimple et CDuree par double dérivation publique. Comme précédemment, le constructeur par copie et l'opérateur d'affectation = doivent être interdits, et le constructeur restant doit être modifié. Ne pas oublier la surcharge de GetSuivant().
Attention : les données membres des classes mères sont par défaut private, ce qui pourrait poser certains problèmes ...
Recopier le fichier exo_04.cxx dans exo_05.cxx. Transformer la fonction Exo_04() en Exo_05() en utilisant la nouvelle classe CDureeAndLink.
Recopier le fichier Makefile_04 dans le fichier Makefile_05, ajouter les variables de dépendance CDUREEANDLINK_05_H, CDUREEANDLINK_05_HXX, la compilation de CDuree.cxx et ajout de CDuree.o à l'édition de liens).
Compiler et tester.
Remarque : cette technique est inapplicable si l'information est d'un type prédéfini : il est par exemple impossible de faire une liste d'entiers int.
Corrigés : exo_05.cxx - CDureeAndLink_05.h - CDureeAndLink_05.hxx - Makefile_05
Nous avons vu au cours des exercices précédents que la liste était constituée de deux "objets", un pointeur vers le premier élément de la liste et l'ensemble des éléments chaînés entre eux. Il est en général maladroit et source potentielle d'erreurs de séparer ces deux informations. Une solution consiste à créer une classe CListeSimple qui contient la tête de la liste, et de la doter de fonctions membres permettant d'accéder indirectement aux éléments chaînés. Elle sera mise en œuvre dans les TDs suivants. Une autre solution, très simple, consiste à ajouter dans la classe CLinkSimple, une donnée membre statique m_Tete, initialisée par le constructeur, et mise à jour à chaque ajout ou chaque suppression. C'est cette dernière solution que nous adopterons ici.
Deux possibilités de mise en oeuvre peuvent être envisagées :
Recopier les fichiers CLinkSimple.h et CLinkSimple.hxx dans CTeteLinkSimple.h et CTeteLinkSimple.hxx. Ajouter à la classe :
Recopier les fichiers CDureeAndLink_05.h et CDureeAndLink_05.hxx dans les fichiers CDureeAndLink.h et CDureeAndLink.hxx. Faire les modifications nécessaires concernant la tête de la liste.
Recopier le fichier exo_05.cxx dans le fichier exo_06.cxx, et faire les modifications nécessaires.
Recopier le fichier Makefile_05 dans le fichier Makefile_06, le mettre à jour.
Compiler et tester.
Remarque : cette solution est inapplicable s'il doit y avoir plusieurs instanciations de la classe CTeteLinkSimple (il ne peut y avoir qu'une seule tête pour plusieurs listes).
Corrigés : exo_06.cxx - CTeteLinkSimple.h - CTeteLinkSimple.hxx - CTeteLinkSimple.cxx - CDureeAndLink.h - CDureeAndLink.hxx - Makefile_06
Toutes les listes construites précédemment sont appelées listes propriétaires. Cela signifie que l'information est dupliquée (valeur recopiée par le constructeur) dans la donnée membre m_Info lorsqu'elle est insérée dans la liste, et que cette copie est détruite, et donc perdue, lorsqu'est détruit l'élément de la chaîne qui la contient.
Ce type de structure (c'est généralisable à tout autre type : arbre, graphe, etc...) ou de conteneur (dictionnaire, pile, etc...) présente trois inconvénients :
La liste est dite non propriétaire. Elle est illustrée par la figure 8 :
En réutilisant la classe générique template <class T> class CElemGen écrite dans l'exercice 2, deux solutions sont possibles :
N'importe quel fichier Makefile peut être utilisé. Compiler et tester.
Corrigés : exo_07.cxx
© D. Mathieu
mathieu@romarin.univ-aix.fr
I.U.T.d'Aix en Provence - Département Informatique