C++ : TP 11 - Conteneurs : la pile

© D. Mathieu    mathieu@romarin.univ-aix.fr
I.U.T.d'Aix en Provence - Département Informatique
Créé le 29/02/2000 - Dernière mise à jour : 15/03/2001

Remarques préliminaires :

Sommaire

Pile générique implémentée par un tableau de taille fixe  exo_01
Pile générique implémentée par un tableau de taille générique  exo_02
Pile générique implémentée par un tableau dynamique de taille fixe  exo_03
Pile générique implémentée par un tableau dynamique de taille variable  exo_04
Constructeur par copie et affectation  exo_05
Classe standard stack  exo_06


Pile générique implémentée par un tableau de taille fixe

exo_01

    Un tableau est une structure de données parmi d'autres, qui peut être utilisée pour implémenter une pile. Dans ce premier exercice, nous l'utiliserons de la façon la plus simple.

    Dans les fichiers CStackArray_01.h et CStackArray_01.hxx, écrire la classe CExcStackArray, de la même façon que les classes d'exceptions des différentes classes écrites dans le TP 10 (par exemple pour la classe CConteneur). Ajouter la classe générique CStackArray qui comporte :

    Dans le fichier exo_01.cxx, écrire la fonction Exo_01() qui teste les possibilités de la classe CStackArray. Essayer de lever les différentes exceptions possibles.

    Recopier le fichier Makefile_05 dans Makefile_01. Supprimer toutes les informations inutiles. Ajouter les dépendances dues aux fichiers de la classe CStackArray.

    Compiler et tester.

Corrigés :      exo_01.cxx      -      CStackArray_01.h      -      CStackArray_01.hxx      -      Makefile_01

Sommaire


Pile générique implémentée par un tableau de taille générique

exo_02

    La solution précédente est beaucoup trop rigide. Celle qui est proposée maintenant permet d'avoir des piles génériques de n'importe quelle taille. Il suffit d'ajouter à la classe un paramètre de généricité supplémentaire : la taille du tableau.

    Recopier les fichiers CStackArray_01.h et CStackArray_01.hxx respectivement dans CStackArray_02.hetCStackArray_02.hxx. Ajouter un second paramètre de généricité sous forme d'une constante non signée, avec une valeur par défaut.

    Recopier le fichier exo_01.cxx dans exo_02.cxx. Modifier la déclaration de la pile.

    Recopier le fichier Makefile_01 dans Makefile_02. Ajouter les dépendances dues aux nouveaux fichiers de la classe CStackArray.

    Compiler et tester.

Corrigés :      exo_02.cxx      -      CStackArray_02.h      -      CStackArray_02.hxx      -      Makefile_02

Sommaire


Pile générique implémentée par un tableau dynamique de taille fixe

exo_03

    Bien que plus souple, la solution ci-dessus est elle aussi à éviter car le code risquerait d'être dupliqué pour des piles qui ne seraient différentes que par la taille. Nous envisageons donc une nouvelle solution : celle d'un tableau alloué dynamiquement dans le constructeur, et dont la taille est passée en paramètre.

    Recopier les fichiers CStackArray_02.h et CStackArray_02.hxx respectivement dans CStackArray_03.h et CStackArray_03.hxx. Supprimer le second paramètre de généricité.

    Dans la classe CStackArray :

Remarque :

La classe CStackArray contient maintenant un pointeur. Le constructeur par copie et l'affectation standard ne peuvent plus être utilisés car ils ne dupliquent que les données membres directement contenues dans l'objet, mais pas les parties "externes" pointées. Dans cette version, on se contentera d'interdire ces deux fonction.

    Recopier le fichier exo_02.cxx dans exo_03.cxx. Modifier la création de la pile.

    Recopier le fichier Makefile_02 dans Makefile_03. Ajouter les dépendances dues aux nouveaux fichiers de la classe CStackArray.

    Compiler et tester.

Corrigés :      exo_03.cxx      -      CStackArray_03.h      -      CStackArray_03.hxx      -      Makefile_03

Sommaire


Pile générique implémentée par un tableau dynamique de taille variable

exo_04

    Nous allons envisager ici que la taille du vecteur passée en paramètre du constructeur est une taille initiale. Dans ce cas, on peut imaginer que, lors d'une tentative d'empilement dans une pile pleine, est alloué un nouveau vecteur de taille plus grande (incrémentée par exemple de la valeur initiale, ou doublée à chaque fois), l'ancien est recopié dedans puis libéré. La seule limite devient l'espace disponible en mémoire dynamique. L'exception bad_alloc peut être levée soit par le constructeur, soit par la fonction Empiler(). la classe CStackArray lèvera l'exception CstMemInsuff dans le premier cas, CstPilePleine dans le second.

Attention : plusieurs méthodes de la classe CStackArray sont susceptibles d'échouer. S'il s'agit de constructeur(s), l'objet n'est pas construit. Dans le cas général d'un échec de constructeur, il faut s'assurer qu'aucune allocation mémoire n'a été effectuée avant cet échec, ou qu'elle a été libérée avant la sortie du constructeur. En effet, le destructeur ne sera jamais appelé. Dans le cas d'une autre méthode, il faut s'assurer que l'objet récupéré par l'utilisateur n'est pas modifié par la méthode.

Remarque : il est brutal de lever une exception dès que la taille du vecteur ne peut pas être doublée. En effet, il est peut-être possible d'augmenter la taille de 50%, ou 25%, etc., ou seulement d'un seul élément. On peut donc faire des tentatives d'allongement de plus en plus réduites, jusqu'à n'essayer d'ajouter qu'un seul élément.

    Pour rendre notre classe encore plus souple, les deux dernières possibilités pourraient être réunies : une taille non nulle passée en paramètre serait une taille fixe, une taille nulle signifierait une taille modifiable automatiquement selon les besoins à partir d'une valeur initiale par défaut. D'autres paramètres pourraient être ajoutés ultérieurement, à condition qu'ils aient tous des valeurs par défaut, indiquant la valeur de l'incrément, si la taille est fixe ou adaptable, etc.

    Lorsque l'utilisateur veut empiler et que le tableau est plein, il est proposé de doubler la taille du tableau. Cela nécessite de conserver la taille courante du vecteur, dans une nouvelle donnée membre m_capacity.

    Une donnée membre étant un pointeur, il est nécessaire d'interdire provisoirement le constructeur par copie et l'affectation. Ces deux fonctions seront implémentées dans le prochain exercice.

    Il est logique de penser que, puisque la taille du vecteur est augmentée chaque fois que le vecteur est plein, elle devrait aussi être réduite lorsque la pile se vide. On pourrait par exemple diviser la taille du vecteur par deux chaque fois qu'il est à plus de la moitié vide. Ce serait une très mauvaise idée car, dans le cas où une succession d'empilement et de dépilement se produirait autour de cette taille critique, le conteneur passerait la majorité de son temps à allouer/désallouer et recopier le vecteur. Une telle solution nécessiterait un mécanisme "d'amortissement".

    Nous adopterons ici la solution utilisée dans de nombreuses bibliothèques, consistant à ne réduire la taille du vecteur qu'à la demande, en ajoutant au conteneur CStackArray la fonction resize() dont le paramètre indique la nouvelle taille du vecteur. Quatre cas doivent être envisagés :

    Un inconvénient très pervers de la réduction de la taille du vecteur, automatique ou volontaire, peut apparaître lorsque le conteneur alloue un vecteur de taille réduite au moyen de l'opérateur new. Il peut en effet arriver, à cet instant, que la mémoire libre soit insuffisante, et qu'une exception bad_alloc soit levée : la libération de la mémoire échoue par manque de mémoire !!! Il convient, dans ce cas, de ne pas redimensionner. Les conteneurs standard offrent une fonction supplémentaire, capacity(), qui permet de connaître le taux d'occupation de la mémoire qui peut être défini par : size() / capacity() * 100 %. Ajouter la fonction capacity() à la classe CStackArray.

    Recopier les fichiers CStackArray_03.h et CStackArray_03.hxx respectivement dans CStackArray_04.h et CStackArray_04.hxx. Que ce soit pour la fonction Empiler() ou pour la fonction resize(), la même séquence doit être répétée :

    A la classe CStackArray ajouter la fonction privée Realloc() de profil :
 
void Realloc (int Increment) throw (std::bad_alloc);

Increment est la valeur algébrique de l'augmentation de taille du tableau, et dont le comportement est décrit ci-dessus.

    Modifier la fonction Empiler() pour gérer l'augmentation automatique du tableau décrite dans la remarque en utilisant la fonction Realloc().

    Ajouter la fonction resize() de profil :
 
void resize (unsigned NewSize) throw (CExcStackArray);

NewSize est la nouvelle taille du tableau, conformément aux indications ci-dessus.

    Recopier le fichier Makefile_03 dans Makefile_04, et ajouter les dépendances dues aux fichiers CStackArray_04.*. Vous pouvez recopier le fichier de test exo_04.cxx, disponible dans les corrigés.

    Compiler et tester.

Corrigés :      exo_04.cxx      -      CStackArray_04.h      -      CStackArray_04.hxx      -      Makefile_04

Sommaire


Constructeur par copie et affectation

exo_05

    Une donnée membre de la classe CStackArray est un pointeur (vers un tableau). Le constructeur par copie qui est fourni à cette classe par le compilateur est très simple et ne donne pas ici le résultat escompté : chaque donnée membre de l'objet source est recopié dans les données membres de l'objet destination. Il y a donc deux pointeurs vers le même vecteur. Lorsqu'on empile ou dépile sur l'une des deux piles, l'autre est aussi affectée. Il en est de même pour l'opérateur d'affectation. Il est donc nécessaire :

    Recopier les fichiers CStackArray_04.h et CStackArray_04.hxx respectivement dans CStackArray_05.h et CStackArray_05.hxx. Faire passer les déclarations des deux fonctions de la partie privée dans la partie publique et les implémenter. Si la capacité de la pile réceptrice est suffisante pour recevoir les éléments de la pile émettrice, il n'est pas nécessaire de la redimensionner à la taille exacte nécessaire. L'utilisateur peut, s'il le souhaite, effectuer cette opération, grâce aux fonctions capacity() et resize().

    Les deux méthodes sont très semblables. On peut donc écrire le constructeur par copie en utilisant "astucieusement" l'opérateur d'affectation.

    Recopier le fichier Makefile_04 dans Makefile_05, et ajouter les dépendances dues aux fichiers CStackArray_05.*.

    Recopier le fichier exo_04.cxx dans exo_05.cxx en supprimant les parties inutiles, et en testant les deux nouvelles méthodes.

    Compiler et tester.

Corrigés :      exo_05.cxx      -      CStackArray_05.h      -      CStackArray_05.hxx      -      Makefile_05

Sommaire


Classe standard stack

exo_06

    La bibliothèque standard C++ offre une classe générique stack avec deux paramètres de généricité :     Dans le fichier exo_06.cxx, instancier la classe générique stack au moyen d'un vector d'entiers et essayer les différentes possibilités.

    Compiler et tester.

Corrigés :      exo_06.cxx

Sommaire


Téléchargement des corrigés :

tppile.zip

Chemins d'accès aux sources des corrigés :

~mathieu/PARTAGE/src/tp/tpC++/tppile/dirpile

© D. Mathieu     mathieu@romarin.univ-aix.fr
I.U.T.d'Aix en Provence - Département Informatique