11ième Classe (Mardi, 18 novembre) CSI2572
Les exceptions en C++ Au cours des exemples précédents, on a souvent rencontré des fonctions qui retournent en principe une valeur, mais qui devraient avoir un comportement particulier en cas d'erreur : template <class T> Table{ T *tab; int sz; public: Table(int n){ tab=new T[sz=n]; // que faire si tab==0 ? } T& operator[](int i){ if(i<sz && i>=0) return tab[i]; // sinon ?? } //... };
Les exceptions en C++ Une solution possible serait dans ces deux cas de considérer qu'il s'agit d'une erreur fatale, et donc, de tout arrêter, c'est ce que fait la fonction assert : //... Table(int n){ tab=new T[sz=n]; assert(tab != 0); } T& operator[](int i){ assert(i<sz && i>=0); return tab[i];
Les exceptions en C++ Table(int n){ tab=new T[sz=n]; if(tab==0) throw Alloc(); } //... T& operator[](int i){ if(i<sz && i>=0) return tab[i]; else throw Interval(); En C++, les exceptions permettront le traitement de ces conditions anormales. On définit à certains points du programme des traite-exceptions, et si une condition anormale apparaît, une exception sera levée, et le contrôle du programme sera transféré jusqu'au premier (c'est-à-dire le plus récent) traite-exception rencontré. Le C++, va permettre d'associer à une exception une expression (et donc un type) qui permettra de sélectionner le traite-exception associé.
Les exceptions en C++ Une exception a un type, quand une exception est lancée, un objet temporaire statique du type de l'exception est créé et initialisé. Le contrôle sera transféré jusqu'au traite-exception, le plus proche, qui peut correspondre à l'expression de l'exception lancée. Le traite-exception le plus proche est celui correspondant au ``try-bloc'' dans lequel le contrôle est entré le plus récemment. Une fois ce traite-exception atteint, tout se passe comme pour le passage des paramètres. Le contrôle reprendra après son cours normal. Si aucun traite-exception approprié n'existe, après le parcours complet du contrôle du programme, une fonction spéciale terminate sera appelée, qui termine l'exécution.
Les exceptions en C++ L'exception doit alors être traitée par le gestionnaire d'exception correspondant. On ne peut attraper que les exceptions qui sont apparues dans une zone de code limitée (cette zone est dite protégée contre les erreurs d'exécution), pas sur tout un programme. On doit donc placer le code susceptible de lancer une exception d'un bloc d'instructions particulier. Ce bloc est introduit avec le mot clé try : try { // Code susceptible de générer des exceptions... }
Les exceptions en C++ Les gestionnaires d'exceptions doivent suivre le bloc try. Ils sont introduits avec le mot clé catch : catch (classe [&][temp]) { // Traitement de l'exception associée à la classe }
Les exceptions en C++ class Excep_Gen{ public: virtual void avertir(){ cerr<<"exception generale\n"; } //... }; En utilisant les classes, en particulier les constructeurs et l'héritage, on peut structurer les exceptions en utilisant l'héritage et la liaison dynamique. Par exemple, on pourrait avoir :
Les exceptions en C++ class Alloc:public Excep_Gen{ size_t demande; Alloc(int n){ demande=n; } void avertir(){ Excep_Gen::avertir(); cerr<< "allocation "<<demande<<" refusee "; } };
Les exceptions en C++ template <class T> class Table{ // ... comme avant public: Table(int n){ tab=new T[sz=n]; if(tab==0) throw Alloc(n); } T& operator[](int i){ if(i<sz && i>=0) return tab[i]; else throw Interval(); //… }; Qui pourrait ensuite être utilisé comme suis à l'intérieur de la classe:
Les exceptions en C++ void f(){ try{ Table<int> t[1000]; // peut provoquer Alloc //... t[i]=t[j] ; // peut provoquer Interval } catch(Alloc & a){ a.avertir(); // traitement pour une erreur d'allocation catch(Interval){ cerr<<"erreur interval"; En C++, on lancera (throw) un exception par une throw-expression. Une partie de programme sera associée à un traite-exception par un try-bloc, à la fin du try-bloc, un traite-exception (introduit par le mot clé catch) définira le comportement en cas d'exception lancée dans le bloc Un client de la classe peut maintenant utiliser le mechanisme d'exceptions de la manière suivante:
Les exceptions en C++ Dans le cas où aucun traite-exception n'est trouvé, la fonction terminate est appelée . Par défaut, elle provoque la terminaison définitive du programme en appelant la fonction abort(). abort appelle la dernière fonction passée en argument à set_terminate() (déclarée comme fonction ayant comme argument void(*pf)(), et retournant aussi un void(*pf)()). Cette dernière fonction est une fonction dont on ne revient pas ...
Spécification d'exceptions dans les fonctions Les exceptions que peuvent générer les fonctions sont une caractéristique importante de ces fonctions, il est donc utile qu'elles figurent dans l'interface de cette fonction: pour un utilisateur de fonction, afin de pouvoir traiter les comportements ``anormaux'' il faut connaître les exceptions que peuvent lever cette fonction. On peut donc en C++ préciser dans l'entête de la fonction les exceptions qui pourront être levées à partir de cette fonction. Exemple: void f() throw (erreur, char *p, int){ //... }
Spécification d'exceptions dans les fonctions f ne pourra lever que des exceptions des types précisés après le throw, cette déclaration permet de définir correctement l'interface avec d'autres fonctions en tenant compte des exceptions. Si une exception non prévue dans la liste apparaît, la fonction unexpected() sera appelée. S'il n'y a pas de spécification d'exception, toutes les exceptions possibles sont autorisées : void f() throw() //aucune exception { //... }; void g() //toutes exceptions { //...
exception exemple I class erreur{ //... public: erreur(const char *p){ } }; double racine(double d){ if(d<0) throw "arg. negatif"; return(sqrt(d));
exception exemple I void essai(double d){ double r; try{ r=racine(d); // ... } catch(const char *p){ cerr<<p<<endl; throw erreur(p); Dans le cas d'un appel de essai avec un argument négatif, une exception sera levée dans racine, qui sera traitée dans essai, et qui lèvera elle-même une exception de type erreur.