Chapitre XI Gestion des erreurs et exceptions
2 La gestion des erreurs et exceptions De nombreux problèmes peuvent survenir pendant lexécution dun programme: -insuffisance de mémoire, disque plein, -perte dun fichier, imprimante non branchée ou à court de papier, -saisie non valide dune valeur, -une fonction ou une classe qui fonctionnent mal, -etc. Rôle des programmeurs face à ces erreurs dexécution: -prévoir ces erreurs, -une application étant construite à partir dun enchaînement dappels de fonctions, toute fonction pouvant être confrontée à une erreur dexécution, il devient nécessaire de pouvoir gérer les appels (les erreurs) en cascade de fonctions -en informer lutilisateur, sauver son travail et arrêter le programme de façon contrôlée, -éventuellement, de mettre en œuvre des solutions de reprises et de correction.
3 Mise en œuvre de la gestion des erreurs dexécution Ignorer lerreur Utiliser une variable globale qui sera initialisée par la fonction provoquant lerreur dexécution. Le contenu de cette variable sera ensuite récupéré et traité par la fonction appelante de celle qui a entraîné lerreur dexécution. Les variables globales noffrent aucune garantie de sécurité. Cela signifie que toutes les fonctions dun programme peuvent y accéder sans restriction.**** à éviter **** PROBLÈME RENCONTRÉ : le code qui détecte lerreur ne peut rien faire pour sauver le travail de lusager et quitter élégamment. Programmation défensive Prendre la décision de ne rien faire en cas de demande erronée. Ex. : Ne pas insérer un nouvel élément dans une pile pleine. Dans certains cas, on ne peut ignorer lerreur parce que lopération ne peut être complétée.Ex. : lors dun calcul. Peut masquer des erreurs importantes de programmation Supposer que les erreurs ne se produiront pas est une mauvaise approche.
4 Mise en œuvre de la gestion des erreurs dexécution Imprimer un message derreur et / ou arrêter le programme - Le code de traitement des erreurs est dispersé et imbriqué tout au long du code du système. - Les erreurs sont gérées là où ces erreurs sont le plus susceptibles de se produire. Avantage:En lisant le code, on peut voir le traitement derreur dans le voisinage immédiat du code et déterminer si une vérification adéquate des erreurs a été mise en place. Désavantage:Le code est « pollué » par le traitement derreurs. Cela rend le code plus difficile à comprendre et à maintenir. Envisageable au cours de la phase de mise au point seulement.**** à éviter ****
5 Mise en œuvre de la gestion des erreurs dexécution Arrêt de lexécution grâce à des assertions Ex. : Un message avisant lusager que la mémoire disponible est insuffisante permettrait à celui-ci de fermer des applications libérant de la mémoire supplémentaire. - Peut entraîner irritation et frustration. - Lusager ne devrait recevoir que des messages ou des avertissements qui ont du sens à son niveau et sur lesquels il peut prendre action. Une assertion est une expression booléenne évaluée lors de lexécution. Si lexpression est évaluée comme fausse, un message derreur de diagnostic est imprimé et le programme sarrête. Cela comprend généralement le texte de lassertion ayant échoué ainsi que le nom de fichier et le numéro de ligne où apparaît lassertion.
6 Mise en œuvre de la gestion des erreurs dexécution Exemple :#include... double valeur_future(double solde_initial, double p, int n) { assert(p >= 0);assert(n >= 0); return solde_initial * pow(1 + p / 100, n); } Inconvénient : Ce mécanisme peut être désactivé selon la volonté du programmeur. Si cela est fait pour des raisons de performance dans les programmes de production, on opte pour une politique à courte vue. Plus important, mettre fin à lexécution est souvent une méthode trop radicale pour répondre à une erreur. De nombreuses erreurs peuvent être gérées si elles sont signalées de façon à ce quun programmeur puisse les détecter et les analyser.
7 Mise en œuvre de la gestion des erreurs dexécution Approche classique dinterception des erreurs: retour de code derreur - des fonctions symbolisent les traitements à mettre en œuvre; -ces fonctions renvoient des valeurs qui peuvent servir à déterminer le succès ou léchec du traitement; -on peut tester les valeurs de retour des fonctions et réagir en conséquence aux erreurs dexécution.
Chapitre XI - Gestion des erreurs et exceptions8 Utilisation des valeurs de retour des fonctions #include int Fonction_1(………….) { ……... // Retourne 0 si aucune erreur nest détectée; un entier positif autrement. …….. } ………... int Fonction_m(………….) { ……... // Retourne 0 si aucune erreur nest détectée; un entier positif autrement. …….. }
Chapitre XI - Gestion des erreurs et exceptions9 Utilisation des valeurs de retour des fonctions int main() { ………. // Appel de la fonction Fonction_i. // Si une erreur est détectée, alorsgérer cette erreur en lien avec la fonction Fonction_i sinonappel de la fonction Fonction_j si une erreur est détectée, alors gérer cette erreur en lien avec Fonction_j sinon ……….. ………. } Cela met en évidence larchitecture nécessaire à des appels de fonctions en cascade qui peuvent entraîner une erreur dexécution.
Chapitre XI - Gestion des erreurs et exceptions10 Utilisation des valeurs de retour des fonctions Dans ce contexte, les appels de fonctions sont séparés par un test (if) qui permet de vérifier la validité de lappel précédent. Architecture peu lisible et surtout très pénible à maintenir (if-else imbriqué qui alourdit lécriture et la mise à jour du programme). #include enum Nom_des_fonctions {Fn_A, Fn_B}; int Fonction_A(float v) { if (v < 0.0) return 1; if (v == 0.0) return 2; // Traitement. return 0; } int Fonction_B(char c) { if (c == ' ') return 1; if ((c 'Z')) return 3; // Traitement. return 0; }
Chapitre XI - Gestion des erreurs et exceptions11 Utilisation des valeurs de retour des fonctions int Gestion_des_erreurs(Nom_des_fonctions Nom, int code_erreur) { if (Nom== Fn_A) switch(code_erreur) { case 0 : cout << "Parfait";return 0; case 1 : cout<<"Erreur 1"; return 1; // Erreur grave case 2 : cout << "Erreur 2"; return 0; default: cout << "Erreur"; return 0; } else
Chapitre XI - Gestion des erreurs et exceptions12 Utilisation des valeurs de retour des fonctions if (Nom== Fn_B) switch(code_erreur) { case 0 : cout << "Parfait";return 0; case 3 : cout <<"Erreur 3"; return 1; // Erreur grave default: cout << "Erreur"; return 0; } else cout << "incomplet ";return 0; }
Chapitre XI - Gestion des erreurs et exceptions13 Utilisation des valeurs de retour des fonctions int main() { char C = ' '; float U = -3.2f; if (Gestion_des_erreurs(Fn_A, Fonction_A(U))) return 1; if (Gestion_des_erreurs(Fn_B, Fonction_B(C))) return 1; return 0; } Un constructeur ne possédant pas de valeur de retour, cette fonction ne peut pas renvoyer de valeur pour informer la fonction appelante quune erreur sest produite.
Chapitre XI - Gestion des erreurs et exceptions14 Mécanisme des exceptions Solution fiable et standardisée de gestion derreurs dexécution qui ne se fonde pas sur les valeurs de retour des fonctions. - permet de capturer toutes les exceptions dun type donné, - permet de lever des exceptions et transférer le contrôle avec les informations pertinentes à une autre partie du code qui pourra gérer la situation correctement, - en retirant le code de traitement des erreurs du « flot principal » dexécution dun programme, cela permet daméliorer la lisibilité des programmes et faciliter leur maintenance. - permet de gérer nativement les appels de fonctions en cascade, - peut être utilisé dans le cas des constructeurs.
Chapitre XI - Gestion des erreurs et exceptions15 Mécanisme des exceptions Étapes de mise en œuvre: (a)définir une classe dexception (b)lancer lexception (c)intercepter lexception. Note : (i)Le traitement des exceptions est conçu pour traiter les erreurs synchrones (Ex. : division par zéro) mais, non, pour gérer des situations asynchrones (Ex. : fin dune opération dE/S sur disque) lesquelles seront prises en compte par un traitement dinterruption. (ii)Il est bien adapté aux systèmes construits sur la base de composants développés de manière distincte.
Chapitre XI - Gestion des erreurs et exceptions16 Définition dune classe dexception Correspond à une classe C++ qui peut fournir des informations sur une erreur. La définition dune classe dexception est obligatoire pour mettre en œuvre le mécanisme dexception mais il nexiste aucune contrainte particulière pour définir cette classe. Ex.: class Erreur { // Données et fonctions membres permettant de traiter les cas // dexception. // Ex. : Affichage dun message derreur. }
Chapitre XI - Gestion des erreurs et exceptions17 Lancement dune exception Toute fonction qui souhaite lancer une exception doit utiliser un nouvel opérateur du langage C++, throw. Cet opérateur doit être suivi par un objet créé à partir dune classe dexception. But de cet opérateur : °quitter la fonction qui lutilise, °informer la fonction appelante quune exception a été générée. Voir à la prochaine étape comment la fonction appelante peut intercepter cette exception Ex.: void Positive(int v) { if (v<0){ Erreur exc; throw exc; } // Traitement propre à cette fonction réalisé uniquement si lexception // nest pas lancée. }
18 Lancement dune exception Syntaxe simplifiée: void Positive(int v) { if (v<0)throw Erreur(); // Traitement propre à cette fonction réalisé uniquement si lexception // nest pas lancée. } Réalise sur la même ligne la création de lobjet de type Erreur et lenvoi de cet objet avec throw. Un objet dexception est créé sans lui attribuer de nom. Avantageux seulement si vous navez pas besoin dappeler des fonctions membres de la classe dexception. Dans ce cas, le simple fait de savoir quune exception dun type donné a été lancée peut fournir une information suffisante pour que le gestionnaire catch puisse effectuer son travail correctement.
Chapitre XI - Gestion des erreurs et exceptions19 Lancement dune exception Spécification dexception dune fonction Optionnel pour une fonction susceptible denvoyer une ou plusieurs exceptions, il sagit dindiquer dans sa déclaration juste après la liste de ses arguments les exceptions susceptibles dêtre lancées. Ex.: float Racine_carree(float v) throw(Negatif) { // Traitement } Cette fonction est capable de lancer des exceptions correspondant à la classe Negatif. Rôle : -permet au compilateur de contrôler la liste des exceptions envoyées par une fonction, -permettre aux programmeurs didentifier très rapidement la liste des exceptions pouvant être lancées par une fonction. Autrement, le compilateur générera un avertissement.
Chapitre XI - Gestion des erreurs et exceptions20 Interception dune exception Linterception dune exception nest pas obligatoire. Par contre, une exception envoyée et non interceptée provoque la fin de lexécution du programme. Pour intercepter une exception, le C++ fournit le bloc try et un ou plusieurs blocs catch: try { // Traitement renfermant des appels de fonctions pouvant générer // des exceptions. } catch (Classe_Exception Ex) { // Ce bloc sexécute pour une exception de type Classe_Exception. } …….
Chapitre XI - Gestion des erreurs et exceptions21 Interception dune exception catch (...) { // Ce bloc sexécute pour tous les autres types dexceptions. // Il faut toujours mettre catch(…) à la fin de la liste des gestionnaires // qui suivent un bloc try, sans quoi, cela empêche lexécution des // blocs catch qui le suivent. } Pour intercepter les exceptions susceptibles dêtre envoyées par certaines fonctions, vous devez appeler ces fonctions dans le cadre dun bloc try délimité par des accolades. Après le bloc try, il faut obligatoirement spécifier au moins un bloc catch. Les exceptions ne peuvent être lancées que de lintérieur de blocs try. Une exception lancée de lextérieur dun bloc try provoque la fin du programme.
22 Interception dune exception Lorsquune exception est envoyée par une des fonctions appelées dans le bloc try, le mécanisme dexception entraîne : Tous les objets créés dans le bloc try sont détruits. Le programme sort du bloc try juste après la fonction qui a entraîné lexception et nexécute pas les instructions situées après cette fonction. Le C++ exécute dans lordre, soit le bloc catch correspondant à lexception interceptée sil existe, soit le bloc catch(…). Si aucune de ces conditions nest remplie, cela entraîne la fin dexécution du programme. Dans le cas de linterception dune classe dexception précise, le bloc catch récupère directement une copie de lobjet créé et renvoyé par lopérateur throw. Au lancement, lexception est capturée par le plus proche gestionnaire dexceptions (p/r au bloc try depuis lequel lexception a été lancée) qui spécifie un type approprié.
Chapitre XI - Gestion des erreurs et exceptions23 Interception dune exception Si un des blocs catch a été utilisé, à la fin de son exécution, le programme continue à exécuter les instructions situées après le dernier bloc catch associé à ce bloc try. Il est impossible de retourner au point de lancement en plaçant une instruction return dans un gestionnaire catch. Un tel return provoque le retour à la fonction qui a appelé la fonction contenant le bloc catch. Si, à lexécution, le code du bloc try ne lance pas dexception, alors tous les gestion- naires catch qui suivent immédiatement le bloc try sont évités et lexécution reprend à la première ligne de code suivant les gestionnaires catch.
Chapitre XI - Gestion des erreurs et exceptions24 Interception dune exception Que doit-on mettre en œuvre dans un bloc catch? Tout dépend du niveau derreur de lexception interceptée : * arrêter le programme avec ou sans message utilisateur, * corriger le problème avec ou sans message utilisateur, * uniquement informer lutilisateur. le programmeur ladministrateur du site informatique perte dun fichier, dun disque, etc. mauvaise saisie à lécran, instabilité numérique, etc. libellé affiché à lécran lancement dune impression écriture dans un fichier log etc.
Chapitre XI - Gestion des erreurs et exceptions25 Interception dune exception Que doit-on mettre en œuvre dans un bloc catch? Lors de la capture dune exception, il se peut que des ressources qui avaient été allouées naient pas encore été relâchées dans le bloc try. Le gestionnaire catch doit, si possible, les libérer. De même, le gestionnaire catch doit fermer tout fichier ouvert dans le bloc try.
Chapitre XI - Gestion des erreurs et exceptions26 Valeurs lancées et interceptées Vous pouvez lancer nimporte quel type de valeur, primitive ou objet. Exemple : throw 3; try {... } catch (int a) {... } Ce nest pas une très bonne idée. Cela noffre pas suffisamment dinformations pour expliquer lerreur. Note :Les conversions implicites, comme celles de int en double ou de char * en string ne sont pas effectuées lorsquune valeur est lancée.
Chapitre XI - Gestion des erreurs et exceptions27 Exemple class syndicat { /*Le président d'un syndicat local possède l'information suivante sur ses membres (dont le nombre ne dépassera jamais 100) : -le numéro d'employé (lequel est une identification unique de l'employé correspondant à un nombre de 4 chiffres dont le premier est 6), -son nom,-son prénom,-son adresse, -son traitement annuel -sa cotisation syndicale annuelle (entre 0 et 2%). Chaque composante de la classe syndicat renferme les caractéris- tiques d'un membre.*/ Fichier Syndicat.h
Chapitre XI - Gestion des erreurs et exceptions28 Exemple protected: struct employe { int numero_employe; char nom[20+1], prenom[20+1], adresse[40+1]; float traitement_annuel; float cotisation_annuelle; } ensemble_membres[100]; int Nombre_de_membres; /* Renferme le nombre de syndiqués.*/
Chapitre XI - Gestion des erreurs et exceptions29 Exemple public: syndicat(); /*Permet de créer un syndicat avec aucun membre. Pré - Nil. Post - Le syndicat est créé avec aucun membre.*/ float Calcule_cotisation_annuelle_moyenne(); /*Fournit la moyenne des cotisations annuelles des membres. Pré -Le syndicat a déjà été créé et possède au moins un membre. Post -Retourne la moyenne des cotisations annuelles.*/
Chapitre XI - Gestion des erreurs et exceptions30 Exemple void Inserer_nouveau_membre(int numero_employe, char * nom,char * prenom, char * adresse, float traitement_annuel, float cotisation_annuelle); /*Permet d'ajouter un nouveau membre syndiqué dont les caractéristiques sont passées en paramètres. Pré -Le syndicat a déjà été créé et le nombre d'employés est moindre que 100. L'employé ayant ce numéro n'est pas syndiqué jusqu'à maintenant. Post -Le nouveau membre dont les caractéristiques sont passées en paramètres fait maintenant partie du syndicat.*/ };
Chapitre XI - Gestion des erreurs et exceptions31 Exemple class Erreur { public: int code_erreur; int numero_employe; float cotisation_annuelle; };
Chapitre XI - Gestion des erreurs et exceptions32 Exemple #include #include "syndicat.h" syndicat::syndicat() { Nombre_de_membres = 0; } Fichier Syndicat.cpp
Chapitre XI - Gestion des erreurs et exceptions33 Exemple float syndicat::Calcule_cotisation_annuelle_moyenne() { Erreur Exc; float somme = 0.0f; if (Nombre_de_membres == 0){Exc.code_erreur = 1; throw Exc; }; for (int i = 0; i < Nombre_de_membres; i++) somme += ensemble_membres[i].cotisation_annuelle; return somme / Nombre_de_membres; }
Chapitre XI - Gestion des erreurs et exceptions34 Exemple void syndicat::Inserer_nouveau_membre(int numero_employe, char * nom, char * prenom, char * adresse, float traitement_annuel, float cotisation_annuelle) { Erreur Exc; if (Nombre_de_membres == 100){Exc.code_erreur =2; throw Exc; };
Chapitre XI - Gestion des erreurs et exceptions35 Exemple for (int i = 0; i < Nombre_de_membres; i++) if (ensemble_membres[i].numero_employe == numero_employe) {Exc.code_erreur = 3; throw Exc; }; if ((numero_employe 6999)) { Exc.code_erreur = 4; Exc.numero_employe = numero_employe; throw Exc; };
Chapitre XI - Gestion des erreurs et exceptions36 Exemple if((cotisation_annuelle 2)) { Exc.code_erreur = 5; Exc.cotisation_annuelle = cotisation_annuelle; throw Exc; };
Chapitre XI - Gestion des erreurs et exceptions37 Exemple ensemble_membres[Nombre_de_membres].numero_employe = numero_employe; strcpy(ensemble_membres[Nombre_de_membres].nom, nom); strcpy(ensemble_membres[Nombre_de_membres].prenom, prenom); strcpy(ensemble_membres[Nombre_de_membres].adresse, adresse); ensemble_membres[Nombre_de_membres].traitement_annuel = traitement_annuel; ensemble_membres[Nombre_de_membres].cotisation_annuelle = cotisation_annuelle; Nombre_de_membres += 1; }
Chapitre XI - Gestion des erreurs et exceptions38 Exemple #include #include "syndicat.h" void main() { syndicat S; Fichier Application.cpp
Chapitre XI - Gestion des erreurs et exceptions39 Exemple try {S.Inserer_nouveau_membre(6423, "Poulin", "Luc", "4356 rue Dupre, Quebec", f, 1.97f); S.Inserer_nouveau_membre(6677, "Fortin", "Diane", "4356 rue Duluth, Ste_Foy", f, 0.35f); S.Inserer_nouveau_membre(6820, "Leduc", "Pierre", "56 Lapointe, Cap_Rouge", f, 2.35f); cout << S.Calcule_cotisation_annuelle_moyenne(); }
Chapitre XI - Gestion des erreurs et exceptions40 Exemple catch(Erreur Exc) { switch(Exc.code_erreur) { case 1 : cout << "Il faut au moins un employe"; break; case 2 : cout << "Il y a deja 100 membres.";break; case 3 : cout << "Cet employe est deja membre."; break; case 4 : cout<< "Matricule invalide : " << Exc.numero_employe;break; case 5 : cout << "cotisation invalide : " << Exc.cotisation_annuelle;break; }
Chapitre XI - Gestion des erreurs et exceptions41 Mise en œuvre de plusieurs classes dexception Fichier Syndicat.h Le fichier na pas changé à l exception de la classe Erreur qui a été remplacée par : class Erreur_Numero_employe { private: int numero_employe; public: Erreur_Numero_employe(int Numero_employe); void Afficher(); };
Chapitre XI - Gestion des erreurs et exceptions42 Mise en œuvre de plusieurs classes dexception Fichier Syndicat.h class Erreur_Cotisation { private:float cotisation_annuelle; public:Erreur_Cotisation(float cotisation); void Afficher(); }; class Erreur_Nombre_de_membres { private:int nombre_de_membres; public:Erreur_Nombre_de_membres (int Nombre_de_membres); void Afficher(); };
Chapitre XI - Gestion des erreurs et exceptions43 Mise en œuvre de plusieurs classes dexception Fichier Syndicat.cpp Erreur_Numero_employe::Erreur_Numero_employe (int Numero_employe) { numero_employe = Numero_employe; } Erreur_Nombre_de_membres::Erreur_Nombre_de_membres (int Nombre_de_membres) { nombre_de_membres = Nombre_de_membres; } Erreur_Cotisation::Erreur_Cotisation(float cotisation) { cotisation_annuelle = cotisation; }
Chapitre XI - Gestion des erreurs et exceptions44 Mise en œuvre de plusieurs classes dexception Fichier Syndicat.cpp void Erreur_Numero_employe::Afficher() { cout << "Mauvais numero d'employe: " << numero_employe; } void Erreur_Cotisation:: Afficher() { cout << "Mauvais taux de cotisation : " << cotisation_annuelle; } void Erreur_Nombre_de_membres:: Afficher() { cout << "Le nombre de membres est inadequat : " << nombre_de_membres; }
Chapitre XI - Gestion des erreurs et exceptions45 Mise en œuvre de plusieurs classes dexception Fichier Syndicat.cpp float syndicat::Calcule_cotisation_annuelle_moyenne() { float somme = 0.0f; if (Nombre_de_membres == 0) throw Erreur_Nombre_de_membres(Nombre_de_membres); for (int i = 0; i < Nombre_de_membres; i++) somme += ensemble_membres[i].cotisation_annuelle; return somme / Nombre_de_membres; }
Chapitre XI - Gestion des erreurs et exceptions46 Mise en œuvre de plusieurs classes dexception Fichier Syndicat.cpp void syndicat::Inserer_nouveau_membre (int numero_employe, char * nom, char * prenom, char *adresse, float traitement_annuel, float cotisation_annuelle) { if (Nombre_de_membres == 100) throw Erreur_Nombre_de_membres(Nombre_de_membres); for (int i = 0; i < Nombre_de_membres; i++) if (ensemble_membres[i].numero_employe == numero_employe) throw Erreur_Numero_employe(numero_employe); if ((numero_employe 6999)) throw Erreur_Numero_employe(numero_employe);
Chapitre XI - Gestion des erreurs et exceptions47 Mise en œuvre de plusieurs classes dexception Fichier Syndicat.cpp if((cotisation_annuelle 2)) throw Erreur_Cotisation(cotisation_annuelle); //Ajout dun membre (idem à précédemment) }
Chapitre XI - Gestion des erreurs et exceptions48 Mise en œuvre de plusieurs classes dexception Fichier Application.cpp #include #include "syndicat.h" void main() { syndicat S;
Chapitre XI - Gestion des erreurs et exceptions49 Mise en œuvre de plusieurs classes dexception Fichier Application.cpp try { S.Inserer_nouveau_membre(6423, "Poulin", "Luc", "4356 rue Dupre, Quebec", f, 1.97f); S.Inserer_nouveau_membre(6677, "Fortin", "Diane", "4356 rue Duluth, Sainte_Foy", f, 0.35f); S.Inserer_nouveau_membre(6423, "Leduc", "Pierre", "56 rue Lapointe, Cap_Rouge", f, 1.35f); cout << S.Calcule_cotisation_annuelle_moyenne(); }
Chapitre XI - Gestion des erreurs et exceptions50 Mise en œuvre de plusieurs classes dexception Fichier Application.cpp catch(Erreur_Numero_employe Exc) { Exc.Afficher(); } catch(Erreur_Cotisation Exc) { Exc.Afficher(); } catch(Erreur_Nombre_de_membres Exc) { Exc.Afficher(); }
Chapitre XI - Gestion des erreurs et exceptions51 Mise en œuvre de plusieurs classes dexception Bref, les exceptions sont symbolisées par des classes pour lesquelles vous pouvez intégrer des données et fonctions membres qui permettent de définir une gestion derreur appropriée. Nous pouvons utiliser lhéritage et le polymorphisme pour définir de véritables hiérarchies de classes spécialisées dans le traitement des erreurs. La classe de base de cette arborescence permettra de regrouper les comportements communs à un groupe dexceptions tout en laissant la possibilité de spécialiser les classes dérivées.
Chapitre XI - Gestion des erreurs et exceptions52 Mise en œuvre de plusieurs classes dexception Quand une concordance exacte se produira-t-elle ? Le type du paramètre du gestionnaire catch correspond exactement au type de lobjet lancé si : - ils sont effectivement du même type;ou - le type du paramètre du gestionnaire catch est une classe de base publique de la classe de lobjet lancé;ou - le paramètre du gestionnaire est un type de référence ou de pointeur dune classe de base et lobjet lancé est une référence ou un pointeur dune classe dérivée de cette classe de base.ou - le gestionnaire catch est de la forme catch(…).
Chapitre XI - Gestion des erreurs et exceptions53 Mise en œuvre de plusieurs classes dexception Note : (i)La correspondance de type doit être exacte. Aucune conversion nest effectuée lors de la recherche dun gestionnaire, à lexception des conversions de classe dérivée en classe de base. (ii)Placer un gestionnaire dexceptions avec un type dargument void * avant les gestionnaires dexceptions utilisant dautres types de pointeurs provoque une erreur de logique. Pourquoi ? (iii)Il est possible de lancer des objets const. Dans ce cas, le type de largument du gestionnaire catch doit également être déclaré comme const.
Chapitre XI - Gestion des erreurs et exceptions54 Hiérarchie de classes dexception class Erreur { public:virtual void Afficher() = 0; }; class Erreur_Numero_employe : public Erreur { private: int numero_employe; public: Erreur_Numero_employe(int Numero_employe); virtual void Afficher(); }; Fichier Syndicat.h La classe de base Erreur est introduite : Fonction virtuelle pure qui devra être redéfinie dans toutes les classes dérivées de la classe abstraite Erreur.
Chapitre XI - Gestion des erreurs et exceptions55 Hiérarchie de classes dexception class Erreur_Cotisation : public Erreur { private:float cotisation_annuelle; public:Erreur_Cotisation(float cotisation); virtual void Afficher(); }; class Erreur_Nombre_de_membres : public Erreur { private:int nombre_de_membres; public:Erreur_Nombre_de_membres (int Nombre_de_membres); virtual void Afficher(); }; Fichier Syndicat.h
Chapitre XI - Gestion des erreurs et exceptions56 Hiérarchie de classes dexception Fichier Syndicat.cpp Un seul changement: les objets dexception sont créés dynamiquement avec lopérateur new et cest ladresse dun objet qui est envoyée avec lopérateur throw. float syndicat::Calcule_cotisation_annuelle_moyenne() { float somme = 0.0f; if (Nombre_de_membres == 0) throw new Erreur_Nombre_de_membres (Nombre_de_membres); for (int i = 0; i < Nombre_de_membres; i++) somme += ensemble_membres[i].cotisation_annuelle; return somme / Nombre_de_membres; }
Chapitre XI - Gestion des erreurs et exceptions57 Hiérarchie de classes dexception Fichier Syndicat.cpp void syndicat::Inserer_nouveau_membre (int numero_employe,char * nom, char * prenom, char * adresse, float traitement_annuel, float cotisation_annuelle) { if (Nombre_de_membres == 100) throw new Erreur_Nombre_de_membres(Nombre_de_membres); for (int i = 0; i < Nombre_de_membres; i++) if (ensemble_membres[i].numero_employe == numero_employe) throw new Erreur_Numero_employe(numero_employe); if ((numero_employe 6999)) throw new Erreur_Numero_employe(numero_employe);
Chapitre XI - Gestion des erreurs et exceptions58 Hiérarchie de classes dexception Fichier Syndicat.cpp if((cotisation_annuelle 2)) throw new Erreur_Cotisation(cotisation_annuelle); //Ajout dun membre (idem à précédemment) }
Chapitre XI - Gestion des erreurs et exceptions59 Hiérarchie de classes dexception Fichier Application.cpp #include #include "syndicat.h" void main() { syndicat S;
Chapitre XI - Gestion des erreurs et exceptions60 Hiérarchie de classes dexception Fichier Application.cpp try { S.Inserer_nouveau_membre(6423, "Poulin", "Luc", "4356 rue Dupre, Quebec", f, 1.97f); S.Inserer_nouveau_membre(6677, "Fortin", "Diane", "4356 rue Duluth, Sainte_Foy", f, 0.35f); S.Inserer_nouveau_membre(6423, "Leduc", "Pierre", "56 rue Lapointe, Cap_Rouge", f, 1.35f); cout << S.Calcule_cotisation_annuelle_moyenne(); }
Chapitre XI - Gestion des erreurs et exceptions61 Hiérarchie de classes dexception Fichier Application.cpp catch(Erreur * pExc) { pExc -> Afficher(); delete pExc; } un seul bloc catch pour intercepter les 3 exceptions. Ce bloc, défini pour un pointeur vers la classe de base, sappliquera aux adresses des objets des classes dérivées. La fonction virtuelle Afficher sera appelée par rapport au type dobjet afin dafficher le message derreur adéquat. Lobjet dexception doit être détruit.
Chapitre XI - Gestion des erreurs et exceptions62 Hiérarchie de classes dexception Pour bénéficier des avantages dune arborescence de classes dexceptions, il nest pas nécessaire de créer les objets en dynamique. Contrairement à précédemment, on peut continuer à créer ces objets en statique au moment de lancer lexception avec lopérateur throw. if((cotisation_annuelle 2)) throw Erreur_Cotisation(cotisation_annuelle); Pour bénéficier du polymorphisme, il sera nécessaire de définir un bloc catch pour intercepter une référence à la classe de base de la hiérarchie. catch(Erreur & Exc) { Exc.Afficher(); } Il n est plus nécessaire de détruire explicitement les objets dexception créés.
63 Hiérarchie de classes dexception Il se peut que plusieurs gestionnaires dexceptions fournissent une correspondance acceptable au type de lexception lancée. Ex. : (i)Un gestionnaire catch(…) interceptant toutes les exceptions peut être présent. (ii)Il est possible quun objet dérivé dune classe soit capturé par un gestionnaire spécifiant le type dérivé ou par des gestionnaires spécifiant le type de nimporte quelle classe de base de cette classe dérivée. Dans ce cas, le 1e gestionnaire dexceptions qui concorde avec le type dexception lancé est exécuté. Si plusieurs gestionnaires correspondent et si chacun deux gère lexception de manière différente, alors lordre des gestionnaires définit la manière dont lexception est traitée.
64 Hiérarchie de classes dexception Note : Placer un catch qui capture un objet de classe de base avant un catch qui capture un objet dune classe dérivée de cette classe de base est une erreur de logique. Pourquoi ?
65 Comment écrire un gestionnaire dexceptions (bloc catch)? (a)Ausculter précisément une erreur et décider dappeler terminate (fin anormale du programme). (b)Convertir un type dexceptions en un autre en lançant une exception différente. (c)Effectuer toute restauration utile et reprendre lexécution après le dernier gestionnaire dexceptions. (d)Étudier la situation ayant causé lerreur, retirer la cause de lerreur et tenter un nouvel appel à la fonction doù émanait lexception. (e)Retourner une éventuelle valeur détat à leur environnement.
66 Comment écrire un gestionnaire dexceptions (bloc catch)? (f)Relancer une exception. Le gestionnaire qui intercepte une exception peut décider quil ne peut la traiter. Il peut alors relancer lexception à laide de linstruction throw; Un tel throw, sans argument, relance la même exception. Une exception relancée est détectée par le bloc try le plus proche qui entoure le gestionnaire en cours et est traitée par le gestionnaire dexceptions listé par ce bloc try périphérique. Note : Un gestionnaire catch peut lui-même découvrir une erreur et lancer une exception. Une telle exception ne sera pas traitée par les gestionnaires mais, si possible, par un gestionnaire catch associé au prochain bloc try qui entoure celui en cours.
67 Relancer une exception : exemple #include using namespace std; void lancerException() throw(exception) { // lancer une exception et lintercepter immédiatement. try{cout << "Fonction lancerException\n"; throw exception(); } catch(exception e) { cout << "Exception geree dans la fonction lancerException\n"; throw;// Relancer lexception pour traitement ultérieur. } cout << "Ceci ne devrait pas safficher.\n"; } la classe exception de la bibliothèque de modèles standard définie dans le fichier en-tête.
68 Relancer une exception : exemple void main() { try{lancerException(); cout << "Ceci ne devrait pas safficher.\n"; } catch(exception e) { cout << "Exception gérée dans la fonction main\n"; } cout<< "Le contrôle du programme continue apres capture dans main.\n"; << endl; } Fonction lancerException Exception geree dans la fonction lancerException Exception geree dans la fonction main Le contrôle du programme continue apres capture dans main.
69 Spécification dexceptions dans une fonction Nous pouvons spécifier une liste dexceptions quune fonction peut lancer. Exemple : int g(float m) throw(a, b, c) { // Corps de la fonction. } Une fonction peut lancer les exceptions indiquées ou des types dérivés. Si une exception qui ne fait pas partie de la liste est lancée, un appel à la fonction unexpected (« inattendue ») est effectué. Si la liste est vide, i.e. int g(float m) throw() { … }alors la fonction ne lancera aucune exception. Si la fonction le faisait, un appel à la fonction unexpected serait effectué.
70 Spécification dexceptions dans une fonction Une fonction sans spécification dexceptions peut lancer nimporte quelle exception. int g(float m) { … } Traitement dexceptions inattendues (fonction unexpected) : - Par défaut, cest la fonction terminate. - Une autre fonction peut être spécifiée grâce à : set_unexpected(pointeur vers une fonction).
71 Dépilage dune pile Lorsquune exception est lancée mais pas capturée dans une portée particulière, la pile dappels de fonctions est dépilée et la capture de lexception est tentée dans le bloc try-catch extérieur le plus proche. Lorsque la pile dappels de fonctions est dépilée, la fonction dans laquelle lexception na pas été interceptée est telle que toutes ses variables locales sont détruites et le contrôle revient au point où la fonction a été appelée. - Si ce point du programme se trouve dans un bloc try, la capture dans un bloc catch associé est tentée. - Si ce point du programme nest pas dans un bloc try ou si lexception ny est pas interceptée, le dépilage des appels de fonctions est effectué une fois encore. - Si lexception nest pas interceptée dans le programme, la fonction terminate est appelée.
72 Dépilage dune pile (exemple) #include using namespace std; void fonction3() throw(runtime_error) {throw runtime_error("erreur à lexécution de la fonction3");} void fonction2() throw(runtime_error) {fonction3();} void fonction1() throw(runtime_error) {fonction2();} void main() {try {fonction1();} catch(runtime_error e) {cout << "Exception rencontrée: " << endl << e.what() << endl;} } Exception rencontrée: erreur à lexécution de la fonction3 dépile
73 Hiérarchie de classes dexceptions de la bibliothèque standard: fichier en-tête Hiérarchie de classes dexceptions de la bibliothèque standard: fichier en-tête Projet de norme de C++ Offre le service what() : émet un message derreur approprié. Plusieurs classes dérivées de la classe exception : runtime_error, logic_error, invalid_argument, length_error, out_of_range, overflow_error, underflow_error,... La hiérarchie dexceptions standard est conçue pour servir de point de départ. On peut donc lancer des exceptions standard ou nos propres exceptions. Pour capturer toutes les exceptions qui peuvent être lancées dans un bloc try, utilisez catch(…), et non catch(exception e).
74 Hiérarchie des types dexceptions de la bibliothèque standard
75 Hiérarchie de classes dexceptions de la bibliothèque standard: fichier en-tête Hiérarchie de classes dexceptions de la bibliothèque standard: fichier en-tête Exemple I : On peut créer des objets de ces classes standards. #include using namespace std; double valeur_future(double solde_initial, double p, int n) { if (p < 0 || n < 0) { logic_error description("Le parametre valeur_future est illegal."); throw description; }; return solde_initial * pow(1 + p / 100, n); }... try{... } catch (logic_error & e) {cout << " Erreur de traitement " << e.what() << "\n";}
76 Hiérarchie de classes dexceptions de la bibliothèque standard: fichier en-tête Hiérarchie de classes dexceptions de la bibliothèque standard: fichier en-tête Exemple II :On avertit lusager dun problème et on offre une chance de recommencer avec dautres entrées. void main() { bool plus = true; while (plus) { try { code } catch (logic_error & e) { cout<< "Une erreur de logique est survenue : " << e.what() << "\n" << "Recommencer ? (o / n)"; string entree; getline(cin, entree); if (entree == "n") plus = false; }
77 Hiérarchie de classes dexceptions de la bibliothèque standard: fichier en-tête Hiérarchie de classes dexceptions de la bibliothèque standard: fichier en-tête Exemple III :On emploie lhéritage pour définir nos propres types dexception comme catégories plus spécialisées des classes standards. class ErreurValeurFuture : public logic_error { public : ErreurValeurFuture(string raison); }; ErreurValeurFuture::ErreurValeurFuture(string raison) : logic_error(raison) {... } La fonction valeur_future peut maintenant lancer un objet ErreurValeurFuture : if (p < 0 || n < 0) throw ErreurValeurFuture("parametre illegal"); Un objet de ErreurValeurFuture dérivée de logic_error peut être capturé avec une classe catch(logic_error & e).
78 Hiérarchie de classes dexceptions de la bibliothèque standard: fichier en-tête Hiérarchie de classes dexceptions de la bibliothèque standard: fichier en-tête Vous pouvez aussi fournir une clause catch(ErreurValeurFuture & e) ne capturant que les objets de ErreurValeurFuture et non les autres erreurs de logique. Vous pouvez même faire les deux : try { code } catch (ErreurValeurFuture & e) { gestionnaire 1 } catch (logic_error & e) { gestionnaire 2 }... Lordre est important.
79 Construction dun programme test But : Ce test permet de sassurer quune classe se comporte telle que spécifiée; Permet de détecter toute anomalie le plus tôt possible. Ce test utilise systématiquement linterface publique de la classe. Ce test doit être appliqué autant de fois que lon veut. Ce test doit fournir un rapport dexécution extrêmement simple. succès ou échec Toutes les méthodes publiques de la classe doivent être testées. Les tests pour chaque méthode doivent être indépendants les uns des autres pour éviter les effets de bord. Si on teste une méthode A, on doit se concentrer sur le test de A et prendre pour acquis que les autres méthodes fonctionnent correctement.
Chapitre XI - Gestion des erreurs et exceptions80 Exemple dun programme test Les fichiers « syndicat.h » et « syndicat.cpp » demeurent intacts. Le programme dapplication précédent est remplacé par le programme test suivant. #include #include "Syndicat Hierarchie.h" #include void test_syndicat() { // Immédiatement après la création d'un nouveau syndicat, le nombre de // membres du syndicat doit être égale à 0. syndicat S; assert(S.Nombre_de_membres_du_syndicat() == 0); } assert( )Lorsque la condition logique est fausse, une exception est lancée.
Chapitre XI - Gestion des erreurs et exceptions81 Exemple dun programme test void test_Calcule_cotisation_annuelle_moyenne() { // La fonction testée doit retourner la cotisation annuelle moyenne. syndicat S; S.Inserer_nouveau_membre(6423, "Poulin", "Luc", "4356 rue Dupre, Quebec", f, 1.99f); S.Inserer_nouveau_membre(6677, "Fortin", "Diane", "4356 rue Duluth, Sainte_Foy", f, 0.35f); S.Inserer_nouveau_membre(6433, "Leduc", "Pierre", "56 rue Lapointe, Cap_Rouge", f, 1.35f); assert(S.Calcule_cotisation_annuelle_moyenne() == 1.23f);
Chapitre XI - Gestion des erreurs et exceptions82 Exemple dun programme test // Le syndicat doit posséder au moins un membre. bool CasInvalide = false; try { syndicat V; cout << V.Calcule_cotisation_annuelle_moyenne(); } catch(...) { CasInvalide = true; } assert(CasInvalide); }
Chapitre XI - Gestion des erreurs et exceptions83 Exemple dun programme test void test_Nombre_de_membres_du_syndicat() { // La fonction testée doit retourner le nombre de membres du syndicat. syndicat S; S.Inserer_nouveau_membre(6423, "Poulin", "Luc", "4356 rue Dupre, Quebec", f, 1.99f); S.Inserer_nouveau_membre(6677, "Fortin", "Diane", "4356 rue Duluth, Sainte_Foy", f, 0.35f); S.Inserer_nouveau_membre(6433, "Leduc", "Pierre", "56 rue Lapointe, Cap_Rouge", f, 1.35f); assert(S.Nombre_de_membres_du_syndicat() == 3); }
84 Exemple dun programme test void test_Inserer_nouveau_membre() {// L'employé à insérer n'était pas déjà syndiqué. bool CasInvalide = false; try {syndicat S; S.Inserer_nouveau_membre(6423, "Poulin", "Luc", "4356 rue Dupre, Quebec", f, 1.99f); S.Inserer_nouveau_membre(6423, "Poulin", "Luc", "4356 rue Dupre, Quebec", f, 1.99f); } catch(...) {CasInvalide = true;} assert(CasInvalide); }
Chapitre XI - Gestion des erreurs et exceptions85 Exemple dun programme test void main() { try { test_syndicat(); test_Calcule_cotisation_annuelle_moyenne(); test_Nombre_de_membres_du_syndicat(); test_Inserer_nouveau_membre(); cout<< "Ce test pour la classe syndicat est reussi. " << endl; } catch(...) { cout<< "Le test pour la classe syndicat a echoue. " << endl; }
Chapitre XI - Gestion des erreurs et exceptions86 Programme test FIN Le programme test appelle une fonction de test pour chaque méthode publique de la classe à tester. Dans chacun de ces tests, on doit clairement documenter ce quon veut tester. On doit utiliser des assertions pour vérifier le comportement de la classe vue de lextérieur.