Page de garde C++ Les exceptions Maîtrise dinformatique Février 2002
Les exceptions Les exceptions permettent de déléguer le traitement dune situation exceptionelle à un gestionnaire dexception (handler) adéquat en amont dans la pile dexécution. Une exception peut-être de nimporte quel type (pas nécessairement un descendant dune classe similaire à java.lang.Throwable ), y compris un type primitif (int, char *, …). Section protégée (bloc try ) et gestionnaires (blocs catch ) : try { … } catch ( exception_declaration1 ) { … } [ catch ( exception_declaration2 ) { … }…] Lancement dune exception par le mot-clef throw : throw expression;
Les exceptions : flot de contrôle Lorsquune exception est lancée : 1Les portées emboîtées et les appels de méthodes empilés sont analysées/dépilés jusquà la découverte dun bloc catch ; les objets partiellement ou complètement créés mais non encore détruits des portées visitées sont détruits de manière adéquate dans lordre inverse de leur création, de même que les tableaux dobjets. 2 Les déclarations de type dexception géré par les blocs catch sont analysées dans lordre. Le cas échéant, le contrôle est donné au premier bloc catch dont le type concorde. Ce bloc peut lancer une nouvelle exception ou relancer la même exception par un simple : throw; Si aucun des blocs catch successifs nest satisfaisant, on continue à létape 1. 3 Si aucun bloc catch satisfaisant nest trouvé, ou si une exception survient durant le dépilement, la fonction prédéfinie terminate() est appelée (son comportement par défaut est dappeler abort() ).
Les exceptions : flot de contrôle Le mot-clef try ne modifie pas le flot de contrôle, mais détermine la portée des blocs catch. Si aucune exception nest lancée dans un bloc try, les blocs catch ne sont pas exécutés. Le contrôle ne peut être donné à un bloc catch quà la suite du lancement dune exception concordante. Les types reconnus par les blocs catch doivent être correctement ordonnés (les types les plus spécifiques en premier). Il est possible de capturer nimporte quel type dexception en utilisant les "ellipses" : catch (... ) { … } On ne peut alors pas référencer lexception reçue (puisquon ne connaît strictement rien de son type), mais on peut toujours la relancer avec : throw; Le lancement dune exception déroute de manière irréversible le flot de contrôle : il est impossible de reprendre lexécution à la "continuation" de linstruction throw.
Les exceptions : flot de contrôle Les exceptions : exemple class MathError { public: virtual const char * GetMessage(); }; class OverflowError: public MathError { public: const char * GetMessage(); }; class DivisionByZeroError: public MathError { public: const char * GetMessage(); };
Les exceptions : flot de contrôle Les exceptions : exemple try { … throw DivisionByZeroError(); … } catch ( const OverflowError& e ) { // ici, on traite les erreurs de dépassement de capacité } catch ( const DivisionByZeroError& e ) { // ici, on traite les erreurs de division par zéro } catch ( const MathError& e ) { // ici, on traite les erreurs mathématiques génériques qui ne // sont ni des dépassement de capacité, ni des div. par zéro }
Les exceptions : terminate() La fonction prédéfinie terminate() est appelée lorsquaucun bloc catch adéquat na été trouvé, lorsquune exception a été lançée lors du déroulement de la pile ou lorsque celle-ci est corrompue. Par défaut, terminate() invoque abort(). Lappel de terminate() peut-être remplacé par celui dun autre gestionnaire avec : terminate_handler set_terminate( terminate_handler ph ) throw(); Avec terminate_handler défini par : typedef void ( *terminate_handler )(); set_terminate( … ) renvoie le gestionnaire précédemment installé (possibilité de chaînage). Les gestionnaires " terminate " ne doivent jamais faire de return (quelle serait leur continuation ?).
Les exceptions : déclarations dexceptions lançables Les fonctions peuvent éventuellement déclarer la liste des exceptions quelles sont susceptibles de lancer/propager, en spécifiant, avant le point-virgule, laccolade ouvrante, =0, … : throw ( type1[, type2[,...]] ) // throws en Java Une liste vide indique que la fonction ne lance/propage pas dexception. Labsence de telle déclaration indique que la fonction est susceptible de lancer/propager nimporte quel type dexception. Si une fonction lance/propage une exception ne faisant pas partie des exceptions déclarée, la fonction prédéfinie unexpected() est invoquée (son comportement par défaut est dappeler abort() ). Une telle violation de la déclaration ne peut être détectée, dans le cas général, que lors de lexécution, du fait de lutilisation de fonctions externes C, de la nécessiter de tout recompiler en cas de modification, … Pour ces mêmes raisons, la liste de déclaration dexceptions ne fait pas partie de la signature dune méthode.
Les exceptions : unexpected() La fonction prédéfinie unexpected() est appelée lorsquune exception ne faisant pas partie de la liste de déclaration est lancée/propagée. Par défaut, unexpected() invoque abort(). Lappel de unexpected() peut-être remplacé par celui dun autre gestionnaire avec : unexpected_handler set_unexpected( unexpected_handler uh ) throw(); Avec unexpected_handler défini par : typedef void ( * unexpected_handler )(); set_unexpected( … ) renvoie le gestionnaire précédemment installé (possibilité de chaînage). Les gestionnaires " unexpected " ne doivent jamais faire de return (quelle serait leur continuation ?). MSVC++ 6 & 7 : non supporté.
Hiérarchie des exceptions Même si les exceptions ne sont pas nécessairement des objets, la librairie standard propose une hiérarchie de classe quil est préconisé de respecter (dans le namespace std ). La classe mère de toutes les exceptions : class exception { public: exception() throw(); exception( const exception& right ) throw(); exception& operator=( const exception& right ) throw(); virtual ~exception() throw(); virtual const char * what() const throw(); }; what() renvoie une chaîne de caractères décrivant lexception.
Hiérarchie des exceptions Les classes dérivées de exception : class logic_error: public exception; class invalid_argument: public logic_error; class out_of_range: public logic_error; class length_error: public logic_error; class domain_error: public logic_error; class runtime_error: public exception; class range_error: public runtime_error; class overflow_error: public runtime_error; class underflow_error: public runtime_error;
Les exceptions : allocation mémoire Les instances dexceptions à lancer peuvent être créées : dans le tas : throw new Exception( … ); Eventuels problèmes de mémoire non disponible ! dans la pile (objet local) : throw Exception( … ); Lobjet exception ainsi lancé ne devrait plus être disponible lorsque le bloc catch y accède, puisque la pile a été déroulée. En fait, le bloc catch va recevoir une copie de lexception, à un emplacement garanti accessible. Néanmoins, dans les objets exceptions, veiller à ne pas stocker de pointeurs/références vers des objets locaux qui ne seront plus disponibles une fois la pile déroulée !
Les exceptions : catch par copie ou par référence ? Une exception lancée par throw Exception( … ); Pourra à la fois être traitée par un bloc catch attendant un objet passé par valeur : Le passage par valeur nécessite des copies supplémentaires, voire des conversions de types : seul le passage par référence permet dutiliser au maximum le polymorphisme des exceptions. catch ( [const] Exception e ) Ou par un bloc catch attendant un objet passé par adresse (référence) : catch ( [const] Exception& e ) Modifier une exception attrapée par valeur avant de la relancer ne sert à rien…
Les function-try blocks Pour une séparation maximale du code "normal" et du code de traitement dexceptions, le corps dune fonction peut être un bloc try (avec son ou ses blocs catch afférents) direct : int main() try { … } catch ( … ) { } … MSVC++ 6 : non supporté. MSVC++ 7 : supporté.
Les function-try blocks Cela est surtout nécessaire pour attraper les exceptions lancées par les évaluations des expressions des listes dinitialisations de constructeurs : A::A( … ) try : membre1( expr1 ), membre2( expr2 ), … { // corps du constructeur } catch ( … ) { … } … Sans le principe des function- try blocks, les exceptions lancées par expr… ne pourraient être traitées par le constructeur.
Exceptions et libérations de ressources Evitez la duplication de code de libération de ressources, comme dans : acquérir_la_ressource; try { utiliser_la_ressource; } catch (... ) { libérer_la_ressource; throw; } libérer_la_ressource; Imaginez sil y avait un grand nombre de ressources à acquérir et libérer ! Utilisez plutôt des objets, dont la destruction est garantie même en cas de déroutement à la suite dune exception : « Lacquisition dune ressource est une initialisation, la libération dune ressource est une destruction. »