Remarques préliminaires :
Première classe | exo_01 |
Fichier de test | exo_02 |
Compilation séparée | exo_03 |
Constructeur par défaut | exo_04 |
Traitement des exceptions | exo_05 |
void Exo_?? (void); |
elles sont, par convention, susceptibles de lever n'importe quelle exception. Pour le moment, les seules exceptions envisagées sont :
L'objectif de cet exercice est de construire une classe permettant de gérer une durée de temps (un délai), exprimé soit en secondes, soit en jours, heures, minutes et secondes. Les normes de programmation devront être respectées : identificateurs des données membres, identificateurs des paramètres, terminaisons des classes.
Dans l'espace de noms anonyme, déclarer la classe CDuree qui possède les données membres suivantes :
0 jour(s) 0 heure(s)
1 minute(s) 40 seconde(s)
12 jour(s) 23 heure(s) 59 minute(s) 9 seconde(s) |
Comme le montre l'exemple ci-dessus, prévoir pour les zones numériques un format tel qu'elles soient alignées verticalement, en utilisant le manipulateur standard setw(), plus commode que la fonction membre width(). On pourra utiliser les unités h., j. min., s.
Dans la fonction Exo_01(), construire différentes durées, puis tester les fonctions Incr() et Decr() pour tous les cas intéressants possibles. Afficher chacune des durées générées.
Renommer le fichier Makefile en Makefile_01. Compiler [1] et tester.
Corrigés : exo_01.cxx
Il est lourd de coder "en dur" tous les cas possibles dans le programme de test, et toute modification du jeu d'essais nécessite une nouvelle compilation. Il est aussi inutile de rendre le programme "conversationnel" pour tester au clavier les différents cas. L'expérience montre que la couverture de tests est très incomplète, et surtout qu'après une modification du code, les tests ne sont pas refaits entièrement. La seule solution acceptable est le fichier de test. C'est cette dernière solution que nous allons illustrer, seulement pour le test des constructeurs.
Créer un fichier texte FichDurees, contenant un certain nombre de durées entières.
Recopier le fichiers exo_01.cxx dans exo_02.cxx. Dans la fonction Exo_02(), créer un type "vecteur de durées", et un vecteur de ce type. Dans une boucle, lire chaque durée sur le fichier et remplir le vecteur (en utilisant la fonction push_back()). Après la dernière lecture, balayer le vecteur et afficher chacune des durées dans le format indiqué ci-dessus.
Compiler et tester (en utilisant le même fichier Makefile_01 que précédemment).
Corrigés : exo_02.cxx - FichDurees
Si la classe CDuree est susceptible d'être utilisée dans plus d'un seul fichier, ce qui est déjà le cas, elle doit en être extraite et mise à la disposition de tout utilisateur. De ce fait elle ne peut plus appartenir à un espace de noms anonyme. Comme la norme C++ préconise de ne pas utiliser d'identificateurs globaux, il faut donc placer la classe CDuree (déclaration et définition) dans un espace de noms nommé. Pour le moment, nous nommerons cet espace nsWorking.
De plus, la déclaration de la classe sera placée dans un fichier CDuree.h susceptible d'être inclus dans tout programme utilisant cette classe. Les définitions des méthodes seront au contraire regroupées dans un fichier CDuree.cxx, et compilées une fois pour toutes dans un fichier objet CDuree.o. Ce fichier devra ensuite être utilisé lors de la phase d'édition de liens.
Recopier le fichier exo_02.cxx dans les fichiers CDuree_03.h, CDuree_03.cxx et exo_03.cxx.
Dans le fichier CDuree_03.h, remplacer l'espace de noms anonyme par l'espace de noms nsWorking. Extraire tout ce qui ne concerne pas la déclaration de la classe CDuree (voir définitions et déclarations des classes). Ajouter les directives d'inclusion conditionnelle.
Dans le fichier CDuree_03.cxx, remplacer l'espace de noms anonyme par l'espace de noms nsWorking. Inclure le fichier CDuree_03.h. Extraire tout ce qui ne concerne pas la définition de la classe CDuree.
Du fichier exo_03.cxx, supprimer tout ce qui concerne la classe CDuree et le remplacer par l'inclusion du fichier CDuree_03.h.
Recopier le fichier Makefile_01 dans Makefile_03. Modifier le fichier Makefile_03, pour permettre de compiler séparément les deux fichiers .cxx et effectuer l'édition de liens.
Compiler et tester.
La commande Unix touch permet, dans une de ses options les plus simples, de modifier la date d'un fichier sans pour autant modifier le fichier correspondant.
Après avoir effectué une compilation complète et réussie de l'ensemble, modifier la date de l'un quelconque des fichiers (par exemple CDuree_03.h) :
duo>touch CDuree_03.h |
et verifier quels sont les fichiers qui sont recompilés en relançant la commande make.
Corrigés : exo_03.cxx - CDuree_03.h, - CDuree_03.cxx, - Makefile_03
Recopier les fichier exo_03.cxx, CDuree_03.h, CDuree_03.cxx et Makefile_03 dans exo_04.cxx, CDuree_04.h, CDuree_04.cxx et Makefile_04. Dans la fonction Exo_04(), et afin de ne pas obliger le programme à redimensionner le vecteur de durées à chaque insertion en queue, donner au vecteur une taille suffisante (par manque de temps, on ne vérifiera pas en cours d'exécution), puis le remplir en lisant les indications du fichier.
Modifier le fichier Makefile_04.
Compiler et tester. Vous constatez une erreur de compilation. Les raisons sont expliquées dans "Constructeurs de la classe vector" et dans la "Fonction resize() de la classe vector".
Ajouter des valeurs par défaut aux paramètres du constructeur et des fonctions Incr() et Decr(). Compiler et tester.
Corrigés : exo_04.cxx - CDuree_04.h, - CDuree_04.cxx, - Makefile_04
Une première exception devrait être levée lorsque, dans la fonction Decr(Delta), la valeur de Delta de la fonction est supérieure à m_Duree.
Il y a lieu de revenir sur le constructeur. Le type unsigned long int empêche de passer une constante négative comme paramètre effectif, le compilateur pouvant détecter cette erreur. En revanche, si le paramètre effectif est une variable de type int négative, le compilateur ne peut anticiper l'erreur. A l'exécution, la valeur reçue en paramètre est interprétée comme un nombre positif (en complément à 2) et le résultat est faux mais non signalé. Il est donc préférable d'utiliser un type entier signé pour le paramètre formel, et de vérifier a posteriori si la valeur passée est bien positive ou nulle. Dans le cas contraire, une exception doit être levée.
Recopier les fichier exo_04.cxx, CDuree_04.h, CDuree_04.cxx et Makefile_04 dans exo_05.cxx, CDuree.h, CDuree.cxx et Makefile.
Dans la partie publique de la classe CDuree, ajouter le type énumératif CodErr_t, représentant le type d'erreur. Les constantes de cette énumération peuvent être CstNoError, de valeur nulle, CstErrDecr, et CstErrConstr levée lorsque la valeur passée au constructeur est nulle.
Dans la partie publique de la classe CDuree, ajouter la classe CException, dérivée de la classe standard exception, possédant deux données membres :
virtual const char* what() const throw(); |
qui, au moyen de la fonction c_str() déjà étudiée, renvoie le libellé de l'exception sous la forme d'une NTCTS (dont le type est const char*),
Modifier le type du paramètre du constructeur en un type signé (suite à la remarque ci-dessus).
Modifier le constructeur et la fonction Decr() pour lever des exceptions en cas d'erreur. Puisque, à cause de la levée d'exception, la liste d'initialisation du constructeur ne peut plus être utilisée, et que les initialisations des différentes données membres doivent être effectuées dans le corps du constructeur, il est plus élégant d'utiliser la fonction Normaliser().
Dans la fonction Exo_05(), construire deux blocs try-catch. Dans le premier, construire une durée avec une valeur négative. Capturer l'exception et afficher les informations disponibles la concernant. Dans le second, construire une durée positive, et décrémenter avec une valeur de Delta trop grande. Capturer l'exception, afficher un message différent en fonction du code d'erreur récupéré (schéma de choix), puis relever l'exception. Que se passe-t-il au niveau de la fonction main() ?
Corrigés : exo_05.cxx - CDuree.h, - CDuree.cxx, - Makefile
[1] Lorsque le fichier que doit utiliser la commande make n'est aucun des deux fichiers standard makefile ou Makefile, le nom du fichier doit être indiqué à la commande :
make -f Makefile_01 nom=exo_01
© D. Mathieu
mathieu@romarin.univ-aix.fr
I.U.T.d'Aix en Provence - Département Informatique