1 La gestion des exceptions C++ Un programme peut rencontrer des conditions exceptionnelles qui risquent de compromettre la poursuite de son exécution La détection dun incident et son traitement dans les programmes importants doivent se faire dans des parties différentes du code. Exemple trivial : int *pint; pint = new int; If (pint == NULL) { cout << Manque de mémoire pour pint << endl; exit(1); }
2 La gestion des exceptions C++ La solution moderne du problème : les exceptions. Elles permettent le découplage total de la détection dune anomalie (exception) de son traitement en saffranchissant de la hiérarchie des appels. Une exception est une rupture de séquence déclenchée par une instruction throw comportant une expression (objet) dun type (classe) donné.
3 Gestion de lexception (1) La gestion de lexception se fait en deux étapes : Une erreur détectée va lever une exception (throw) Un gestionnaire va récupérer cette exception et la traiter convenablement (catch) Pour pouvoir détecter les exceptions, les instructions susceptibles de lever des exceptions sont incluses dans un bloc spécifique (try)
4 Gestion de lexception (2) Définition de lexception class exception_triviale { … }; Utilisation et levée de lexception … try { int i = 0; i--; if (i NMAX) { exception_triviale e; throw(e); } … } Interception et traitement de lexception catch (exception_triviale e) { … }
5 Utilisation dun gestionnaire dexception #include typedef int Erreur; class vecteur { protected: int *pespace; int taille; public: vecteur (int _taille){taille=_taille;pespace=new int[taille];} //surcharge d'opérateur d'adressage avec une fonction - membre int & operator[](int i); }; int& vecteur::operator[](int i){ if((i =taille)) throw -1; return pespace[i]; } int main(){ try{ vecteur v(2); v[2]=1; } catch(Erreur){ cerr<<"Indice invalide"<<endl; } return 0; } Resultat : Indice invalide Commentaires : cest une exception avec le passage dun type catch(Erreur), Erreur étant un type Catch peut être interprété comme le nom de la fonction – gestionnaire En fait le bloc catch contient la séquence de traitement correspondante
6 Gestionnaire avec passage dune valeur #include enum Erreurs {depasseG, depasseD}; class vecteur { protected: int *pespace; int taille; public: vecteur (int _taille){taille=_taille;pespace=new int[taille];} //surcharge d'opérateur d'adressage avec une fonction - membre int & operator[](int i); }; int& vecteur::operator[](int i){ if(i<0) throw depasseG; if(i>=taille) throw depasseD; return pespace[i]; } int main(){ try{ vecteur v(2); v[2]=1; } catch(Erreurs e){ if(e==depasseG) cout<<"Indice trop petit"<<endl; if(e==depasseD)cout<<"Indice trop grand"<<endl; } return 0; } Resultat : Indice trop grand Commentaire : gestionnaire avec une valeur permet de gérér les exceptions plus finement
7 Gestion avec un objet-exception class vecteur { protected: int *pespace; int taille; public: class Exception_portee{ public : int indice; Exception_portee(int i){indice=i;} }; vecteur(int_taille){taille=_taille; pespace=new int[taille];} int & operator[](int i); }; int& vecteur::operator[](int i){ if((i =taille)) throw Exception_portee(i); return pespace[i]; } int main(){ try{ vecteur v(2); v[2]=1; } catch(vecteur::Exception_portee e){ cerr<<"Erreur de depassement dans le tableau i= "<<e.indice<<endl; } return 0; } Resultat : Erreur de depassement dans le tableau i= 2 Commentaire : gestionnaire avec une valeur permet de gérér les exceptions plus finement
8 Flot dexécution (UML) Vecteur v Exception_portee e Diagramme de séquence
9 Commentaires Le modèle de gestion des exceptions proposé par C++ ne permet pas de reprendre lexécution à partir de linstruction ayant levé lexception, mais le permet après le bloc catch correspondant Si le bloc try/catch nest pas prévu, lexception déclenchée par throw provoque larrêt de lexécution dans la fonction ou méthode courante.
10 Exemple avec exit #include enum Erreurs {depasseG, depasseD}; class vecteur { protected: int *pespace; int taille; public: vecteur (int _taille){taille=_taille;pespace=new int[taille];} //surcharge d'opérateur d'adressage avec une fonction - membre int & operator[](int i); }; int& vecteur::operator[](int i){ if(i<0) throw depasseG; if(i>=taille) throw depasseD; return pespace[i]; } int main(){ try{ vecteur v(2); v[2]=1; } catch(Erreurs e){ if(e==depasseG) cout<<"Indice trop petit"<<endl; if(e==depasseD)cout<<"Indice trop grand"<<endl; exit(-1); } return 0; } Commentaire : larrêt définitif du programme lors du traitement de lexception
11 Exemple de plusieurs exceptions #include class vecteur { protected: int *pespace; int taille; public: class Exception_creation{ public : int hors_indice; Exception_creation(int i){ hors_indice=i; } }; class Exception_limite{ public : int indice; Exception_limite(int i){ indice=i;} }; vecteur (int _taille){if(_taille<=0) throw Exception_creation(_taille); taille=_taille; pespace=new int[taille];} int & operator[](int i); };// fin de la déclaration de la classe int& vecteur::operator[](int i){ if((i =taille)){ Exception_limite e(i); throw e; } return pespace[i]; }
12 Exemple de plusieurs exceptions int main(){ try{ vecteur v(-2); v[2]=1; } catch(vecteur::Exception_creati on e){ cerr<<"Erreur de depassement a la creation i= "<<e.hors_indice<<endl; } catch (vecteur::Exception_limite e){ cerr<<"Erreur de depassement dans le tableau i= "<<e.indice<<endl; exit(1); } return 0; } Quelle exception est active? Résultat? Erreur de dépassement à la création i = 2 Commentaires : les exceptions peuvent être déclanchées par nimporte quelle fonction car les définitions des classes sont connues.
13 Poursuite dexécution du programme Il est impossible de reprendre lexécution du programme à linstruction suivant le déclenchement de lexception mais Le flot de contrôle peut être repris après le bloccatch Souvent le bloc try couvre toute une fonction de sorte quaprès lexécution dun gestionnaire dexception ne provoquant pas darrêt, il y a retour de la fonction
14 Retour du flot de contrôle Exemple int main(){ try{ vecteur v(-2); v[2]=1; } catch(vecteur::Exception_creation e){ cerr << "Erreur de dépassement à la création i="<< e.indice << endl; } catch (vecteur::Exception_limite e){ cerr << "Erreur de dépassement dans le tableau i= << e.indice << endl; } cout << Reprise après les exceptions" << endl; return 0; } Résultats : Erreur de dépassement à la création i=-2 Reprise après les exceptions
15 Choix du gestionnaire Le gestionnaire reçoit toujours une copie de lexpression passée à throw même sil sagit de la transmission par référence. Exemple : Exception_limite c; throw c; // une copie dobjet c est crée. Lorsquune exception est transmise à un bloc try, on recherche, dans les différents blocs catch associés, un gestionnaire approprié au type de lexpression mentionnée dans linstruction throw. Le mécanisme est le même que pour la recherche des fonctions-membres dans la hiérarchie des classes
16 Prise en compte des sorties des blocs Exemple : void f(int n) { vecteur v1(5); try{ vecteur v2(5); v1[n]=1; //on ne sait pas si on est bien dans les limites } catch (vecteur::Exception_limite e){ cerr << "Err de dépass dans le tableau i= << e.indice << endl; } cout << "Je reprends après les exceptions" << endl; // ici v1 est connu, v2 a été détruit } Le méchanisme de gestion des exceptions appelle le destructeur de tout objet automatique déjà construit et devenant hors de portée
17 Choix du gestionnaire Hiérarchie des classes dexceptions class Exception_vecteur {…}; class Exception_creation : public Exception_vecteur {}; class Exception_limite : public Exception_vecteur{}; // ces classes sont déclarées à lextérieur de la classe vecteur; void f() {… throw Exception_creation; throw Exception_limite; } int main () { try { … f(); … } catch (Exception_vecteur e) { cout<< Interception des deux ici car enfants de la classe Exception_vecteur << endl; }
18 Le cheminement des exceptions Quand une exception est levée par une fonction, on cherche tout dabord un gestionnaire dans léventuel bloc try/catch associé à cette fonction Si lon nen trouve pas, on poursuit la recherche dans un éventuel bloc try/catch associé à une fonction appelante Si aucun gestionnaire dexception nest trouvé on appelle la fonction terminate. Par défaut terminate appelle la fonction abort.
19 Version 1 int main() { try{ f1(); // dépassement de limite! } catch (vecteur::Exception_limite e) { cerr<<"Dans main : Erreur de dépassement dans le tableau i="<<e.indice<<endl; } cout << Reprise après les exceptions" << endl; return 0; } void f1() { try { vecteur v(10); v[10]=0; // Attention ! vecteur v1(-1); } catch (vecteur::Exception_creation e) {// est déclenchée par le constructeur cout<<"Dans f1 : Exception_creation i= "<<e.hors_indice<<endl; } } Résultat : Dans main : Erreur de dépassement dans le tableau i=10 Reprise après les exceptions
20 Version 2 int main() { try { f1(); } catch (vecteur::Exception_limite e) { cerr<<"Dans main : Erreur de dépassement dans le tableau i= "<<e.indice<<endl; } cout << Reprise après les exceptions" << endl; return 0; } void f1() { try{ vecteur v(10); v[9]=0; vecteur v1(-1); } catch (vecteur::Exception_creation e){ cout<<"Dans f1 : Exception_creation i= "<<e.hors_indice<<endl; } } Résultat : Dans f1 : Exception_creation i= -1 Reprise après les exceptions
21 Redéclenchement des excéptions (I) Dans un gestionnaire, linstruction throw (sans expression) retransmet lexception au niveau englobant. catch(..){ … throw; } Cette possibilité permet de compléter le traitement standard dune exception par un traitement complémentaire spécifique
22 Redéclenchement des exceptions (II) #include using namespace std; void f(); void main(void) { try { f(n); } catch (int) { cout << « Exception int dans main" << endl; exit(-1); } cout << "suite bloc try du main" << endl; } void f(){ try { int n; throw n; } catch (int) { cout << « Exception int dans f" << endl; throw; } }//fin void f() Résultat : Exception int dans f Exception int dans main
23 Les exceptions standard C++ Les exceptions standard sont des classes dérivées dune classe de base exception Leur déclaration figure dans le fichier-entête stdexcept Certaines peuvent être déclenchées par des fonctions ou des opérateurs de la bibliothèque standard Example : bad_alloc : échec dallocation mémoire par new
24 Spécification dinterface(I) Une fonction peut spécifier les exceptions quelle est susceptible de déclencher. Elle le fait à laide de throw placé juste après lidentificateur de la fonction void f() throw (A,B){ …} // f est censée ne déclencher que les exceptions A et B Toute exception non prévue et déclenchée à lintérieur de la fonction (ou dune fonction appelée) entraîne lappel de la fonction «unexpected » « unexpected » => terminate =>abort par défaut.
25 Spécification dinterface (II) #include using namespace std; void f(int) throw(int); void main(void) { int n; cout << "Entier (0 a 2) : " ; cin >> n; try { f(n); } catch (int) { cout << "exception int dans main" << endl; } cout << "suite bloc try du main" << endl; } void f(int n) throw(int) { try { cout << "n = " << n << endl; switch (n) { case 0 : { double d = 0; throw d;//traité par f break; } case 1 : { int n = 0; throw n; //traité par main break; } case 2 : { float f = 0; throw f;//appel unexpected break; } catch (double) { cout << "exception double dans f" << endl; } cout << "suite du bloc try dans f et retour appelant" << endl; }
26 Spécification dinterface(III) void f(int n) throw(int) { try { cout << "n = " << n << endl; switch (n) { case 0 : { double d = 0; throw d;//traité par f break; } case 1 : { int n = 0; throw n; //traité par main break; } case 2 : { float f = 0; throw f;//appel unexpected break; } catch (double) { cout << "exception double dans f" << endl; } cout << "suite du bloc try dans f et retour appelant" << endl; } Résultats : Entier (0 à 2) : 0 n=0 exception double dans f Entier (0 à 2) : 1 n=1 exception int dans main Entier (0 à 2) : 2 n=2 Fin anormal ( exception std bad_exception)
27 Les exceptions standard C++ La bibliothèque stadard (stdl) comporte quelques classes qui dérivent dun classe de base exception Logic_error Domain_error Invalid_argument Length_error Out_of_range Runtime_error range_error overflow_error Underflow_error Bad_alloc //échec dallocation mémoire par new Bad_cast //échec de lopérateur dynamic_cast Bad_exception //erreur de spécification dexception (peut être déclenché par unexpected) Bad_typeid //echec de la fonction typeid
28 Les exceptions standard C++(2) #include using namespace std; int tableau[10] = { 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 }; int f(int i); void main(void) { try { int n; cout > n; int i = f(n); cout << "i = " << i << endl; } catch (exception& e) { cout << "Exception : " << e.what() << endl; } int f(int i) { if (i >= 0 && i < (sizeof(tableau) / sizeof(int))) { return tableau[i]; } throw out_of_range("index hors domaine"); } Résultats : Entier : -1 Exception : index hors domaine Entier : 0 i= 10
29 Création des exceptions dérivées de la classe Exception Raisons (1) Faciliter le traitement des exceptions : interception de toute exception avec le simple gestionnaire : catch (exception & e){…} Cela est vérifié pour les exceptions dérivées de la classe exception. (2) Utiliser la fonction what de la classe exception, qui renvoie la chaîne de caractères utilisée par le constructeur de lexception
30 Exemple de la fonction what #include using namespace std; int main(){ try{ throw range_error("anomalie_1"); } catch (range_error &re){ cout << "exception: << re.what() << endl; } return 0; } Résultats : exception: anomalie_1
31 Un vrai exemple dutilisation de « range_error » #include using namespace std; int main(){ try{ int i=100; double epsilon=0.1; double somme=0.0; for(int k=1; k<=100;k=k+10) { epsilon=epsilon/k; if (epsilon<=0.001) throw range_error("anomalie_1"); else {somme=somme+1/epsilon; cout<<"somme courante"<<somme<<endl;} } cout<<"somme = "<<somme<<endl; } catch (range_error &re){ cout<<"exception: "<<re.what()<<endl; } somme courante10 somme courante120 exception: anomalie_1 Press any key to continue Résultat dexecution
32 Utilisation de lexception Bad_Alloc #include "stdafx.h" #include #define MAX_NUMBER using namespace std; int _tmain(int argc, _TCHAR* argv[]) { try { int* ptrTab; for (int i=0;i<MAX_NUMBER; i++) ptrTab=new int[MAX_NUMBER]; } catch (bad_alloc & b) { cout<<"Exception d'allocation de mémoire par new"<<endl; cout<<"Vous êtes trop gourmand!" <<endl<<b.what()<<endl; } return 0; } Exception d'allocation de mémoire par new Vous êtes trop gourmand! bad allocation Press any key to continue lexception « bad-alloc » est déclenchée par la fonction « new » de la librairie standard.
33 Héritage de la classe exception #include using namespace std; class monException1 : public exception { public: monException1() {} // Surcharge de la méthode virtuelle what virtual const char* what() const { return "Mon exception no 1"; } }; class monException2 : public exception { public: monException2(char* texte) { this- >texte = texte; } // Surcharge de la méthode virtuelle what virtual const char* what() const { return texte; } private: char* texte; }; void main(void) { try { cout << "Bloc try 1" << endl; throw monException1(); } catch (exception& e) { cout << "Exception : " << e.what() << endl; } try { cout << "Bloc try 2" << endl; throw monException2("deuxieme type"); } catch (exception& e) { cout << "Exception : " << e.what() << endl; }