© D. Mathieu
mathieu@romarin.univ-aix.fr
I.U.T.d'Aix en Provence - Département Informatique
Créé le 27/03/2001 -
Dernière mise à jour : 01/05/2001
Remarques préliminaires :
Itérateur de liste | exo_01 |
Fonction ForEach() générique | exo_02 |
Allocation de mémoire par granule | exo_03 |
Nous avons indiqué plus haut que les itérateurs internes n'offrent pas suffisamment de possibilités pour parcourir les conteneurs (listes ou vecteurs). En revanche, les itérateurs, utilisés sur la classe générique standard vector dès les premiers TPs, sont largement mis en oeuvre dans la bibliothèque standard. L'objet de cet exercice est de doter le conteneur "liste double" créé dans le TP précédent d'un itérateur simple, pouvant progresser dans les deux sens dans la liste. Nous ne distinguerons pas les cas des const_(reverse)_iterators qui ne présentent aucun intérèt pédagogique supplémentaire, car très simples à ajouter. Comme d'habitude, le but de l'exercice est plus de montrer le principe d'un itérateur que de développer une classe totalement opérationnelle.
Classe CIterAbstr
Indépendamment de l'implémentation du conteneur séquentiel (essentiellement la liste pour nous), et donc de l'itérateur qui le parcourt, on peut définir les caractéristiques que doit avoir ce dernier :
Dans le fichier CIterAbstr_01.h, écrire la classe d'exception CExcIter dans l'espace de noms nsSdD, qui sera associée aux erreurs de manipulation d'itérateurs, selon le modèle habituel (voir par exemple la classe CStackAbstr_01.h). Dans ce même fichier, ajouter la classe générique abstraite CIterAbstr, toujours dans l'espace de noms nsSdD, représentant une interface d'itérateur, et ayant les méthodes virtuelles pures suivantes :
Deux sortes d'erreurs sont prévisibles, indépendamment des implémentations du conteneur et de l'itérateur associé :
vector <int> VInt;
... vector <int>::iterator It; cout << *It << end; // opération interdite It = VInt.begin(); cout << *It << end; // opération autorisée |
A la classe CExcIter ajouter les constantes CstIterInvalide et CstOutOfRange correspondant à ces deux types d'erreurs.
Classe CListAbstr
La classe CListAbstr écrite dans le TP précédent doit être enrichie. En effet, elle doit posséder deux méthodes permettant de positionner un itérateur en début de liste et de vérifier s'il a atteint la fin de la liste.
Dans le fichier CListAbstr_01.h, recopier votre fichier dirliste/CListAbtr_04.h ou le
corrigé.
Supprimer les deux déclarations de type Fct1_t et Fct2_t, ainsi que les trois fonctions ForEach() (les itérateurs les rendent inutiles) et les fichiers inclus associés.
Ajouter les deux membres virtuelles pures de profils suivants (qui seront justifiés ultérieurement) :
virtual const CIterAbstr <T> & begin (void) = 0;
virtual const CIterAbstr <T> & end (void) = 0; |
Classe CIterGen
Les classes d'itérateurs et de conteneurs sont liées entre elles :
CConteneur <U> * m_Conteneur; |
CLink2Gen <U> * m_Courant; |
Pour plus de commodités, le type anonyme CLink2Gen <U> peut évidemment être nommé au moyen de l'instruction typedef ;
Afin d'alléger les fichiers, le conteneur n'implémentera plus que l'interface CListAbstr. Les interfaces de pile, de fifo et de deque sont donc supprimés, et le fichier Makefile simplifié. Afin de gagner du temps, recopier dans les fichier CConteneur_01.h, CConteneur_01.hxx et Makefile_01, les fichiers CConteneur_01.h.init, CConteneur_01.hxx.init et Makefile_01.init du répertoire : ~mathieu/PARTAGE/src/tp/tpC++/tpliste2/dirliste2.
Dans le fichier CConteneur_01.h, entre les classes CExcCont et CConteneur, ajouter les déclarations incomplètes des deux classes CIterGen et CConteneur :
template <class U> class CIterGen;
template <class T> class CConteneur; |
puis la déclaration complète de la classe générique CIterGen dérivée de la classe abstraite d'interface CIterAbstr :
CIterGen (CConteneur <U> * const Conteneur, CLinkT * const Ptr);
void ValiderConteneur (void) const throw (CExcIter); void ValiderIter (const CIterGen & Iter) const throw (CExcIter); |
Remarques
virtual bool
operator == (const CIterAbstr <U> & Iter) const throw (CExcIter); |
et non
virtual bool
operator == (const CIterGen <U> & Iter) const throw (CExcIter); |
comme on aurait pu le penser. Dans l'implémentation de cette fonction, il y aura donc un conflit entre le type du premier opérande (celui sur lequel sera appelé l'opérateur), de type CIterGen <U>, et celui du paramètre, de type CIterAbstr <U>. Un transtypage sera donc nécessaire.
Classe CConteneur
Au début de la classe, ajouter en public la définition du type CIterator, qui est une instanciation du type CIterGen avec le paramètre de généricité du conteneur.
Pour déclarer un itérateur dans son programme, l'utilisateur utilisera ce type :
typedef CConteneur CListIdent * UneListe = new CListIdent;
|
Puisque toute liste doit fournir deux itérateurs, de la classe CIterator, pointant l'un sur le premier élément de la liste (le suivant de la tête), et l'autre au-delà du dernier élément (sur la sentinelle de queue [1]), nous avons choisi d'en faire deux données membres du conteneur, avec pour avantage de ne pas avoir à appeler un constructeur d'itérateur chaque fois que l'une des fonctions begin() ou end() est appelée. Il est donc inutile que ces fonctions en retournent une copie, il suffit qu'elles en retournent la référence, garantissant cependant qu'ils ne peuvent être modifiés par l'utilisateur au moyen du qualifieur const.
A la classe CConteneur, ajouter les données membres privées m_begin et m_end de type CIterator. La première pointe sur le premier élément de la liste (celui qui suit m_Tete) et évolue donc au cours du temps. Elle sera mise à jour à chaque appel de la fonction begin(). En revanche, la seconde est constante et sera initialisée une fois pour toutes dans le constructeur.
Dans le fichier exo_01.cxx, tester les différentes possibilités de parcours d'une liste.
Modifier le fichier Makefile_01 pour prendre en compte la classe CIterAbstr.
Corrigés : exo_01.cxx - CIterAbstr_01.h - CListAbstr_01.h - CConteneur_01.h - CConteneur_01.hxx - Makefile_01
La bibliothèque C++ a très clairement séparé :
Afin de pouvoir appliquer les seconds (les algorithmes) aux premiers (les conteneurs), les concepteurs ont inventé les itérateurs externes, souvent appelés dans la littérature "la glu" car ils font le lien entre les deux. Puisque nous disposons maintenant d'itérateurs, il reste à illustrer la technique de fabrication des algorithmes. Nous l'illustrerons sur la fonction bien connue ForEach().
Puisqu'un conteneur est accessible à partir d'un itérateur instancié, la fonction ForEach() n'a pas besoin de savoir sur quel conteneur elle est appliquée. Il suffit de lui passer deux itérateurs, qui définissent une "tranche" (un intervalle semi-ouvert) valide : les deux itérateurs doivent désigner le même conteneur. On pourrait s'en assurer dans la fonction, mais, pour simplifier, on supposera que c'est de la responsabilité de l'utilisateur (mauvaise supposition ...). Comme précédemment, le traitement à appliquer sera passé en troisième paramètre, sous la forme maintenant bien connue, et maîtrisée, d'un functor. Dans le cas général, les itérateurs peuvent être de type quelconque, pourvu qu'ils soient de même type : [const_][reverse_]iterator sur des listes ou des vectors de n'importe quoi. Le type des deux premiers paramètres est donc le type de généricité de la fonction ForEach().
La fonction ForEach() a un autre paramètre de généricité, implicite : le troisième paramètre est un functor générique instancié exactement par le même type que celui qui a servi à intancier l'itérateur et le conteneur qui lui est associé. Il ne faut pas que l'utilisateur ait à indiquer ce second paramètre de généricité pour trois raisons :
Il est possible et simple de récupérer le type d'un paramètre générique d'une classe, comme le montre l'exemple suivant :
template <typename T>
class CX { // ... public : typedef T value_type; // ... }; // CX typedef CX <char> NouveauType;
|
Recopier le fichiers CIterAbstr_01.h dans CIterAbstr_02.h. Dans la partie publique de la classe, ajouter la déclaration du type value_type (c'est un identificateur standard dans la bibliothèque C++) qui représente le type générique.
Dans les fichiers Algo.h et Algo.hxx, déclarer et définir dans l'espace de nons nsSdD la fonction ForEach() conformément aux indications ci-dessus.
Recopier le fichier dirliste/exo_06.cxx dans exo_02.cxx. Le modifier pour qu'il utilise la nouvelle fonction ForEach().
Recopier le fichier Makefile_01 dans Makefile_02 et ajouter les dépendances dues aux fichiers Algo.*.
Compiler et tester.
Corrigés : exo_02.cxx - Algo.h - Algo.hxx - CIterAbstr_02.h - CListAbstr_02.h - CConteneur_02.h - CConteneur_02.hxx - Makefile_02
[2] La surcharge de l'opérateur -> a été effectuée dans l'exo_05 du TP 10 : "Pointeurs intelligents" : voir les fichiers CStack.h et CStack.hxx
© D. Mathieu
mathieu@romarin.univ-aix.fr
I.U.T.d'Aix en Provence - Département Informatique