© D. Mathieu
mathieu@romarin.univ-aix.fr
I.U.T.d'Aix en Provence - Département Informatique
Créé le 20/12/1999 - Dernière mise à jour : 16/01/2000
Classe exceptionFonctions modifiant le comportement du programme
Classes d'exceptions standard
Classe ios_base::failure
Classe bad_exception
Fonction unexpected()
Fonction uncaught_exception()
TQuelconque ExcX;
// ... try { Traitement (); // contient, directement ou non, "throw ExcX;" } catch (TQuelconque) { TraiteException (); // par exemple message d'erreur } |
enum TExc {CstExcOverflow, CstExcUnderflow};
// ... try { Traitement (); // contient, directement ou non, "throw xxx;" où // xxx est un objet de type TExc } catch (TExc UneExc) { switch (UneExc) { case CstExcOverflow : TraiteOverflow (); break; case CstExcOverflow :
default :
|
ou bien une classe :
class CExc {}; // contient un constructeur
par défaut
// ... try { Traitement (); // contient, directement ou non, "throw CExc();" // qui renvoie l'objet de la classe CExc créé // par l'appel de son constructeur } catch (CExc) { TraitementException (); // par exemple message d'erreur } |
class CList
{ // ... public : // ... class CDebord {}; // contient un constructeur par défaut // ... }; // CList class CTree
}; // CTree class CArray
}; // CArray |
// ...
try { Traitement (); // manipule une liste, un arbre, un tableau // qui peuvent lever une exception CDebord } catch (CList::CDebord) { TraitExcList(); // par exemple message d'erreur } catch (CTree::CDebord) { TraitExcTree(); // par exemple message d'erreur } catch (CArray::CDebord) { TraitExcArray(); // par exemple message d'erreur } |
Si aucune exception n'est levée, les blocs catch sont sautés. Si une des exceptions est levée, le traitement du bloc catch correspondant est effectué et le contrôle passe à la suite du dernier bloc catch.
//...
try { Traitement (); // manipule une liste, un arbre, un tableau // qui peuvent lever une exception CDebord } catch (CTree::CDebord) { TraitExcTree (); // par exemple message d'erreur } catch (...) { TraiExcQuelc (); // ce traitement ne peut évidemment pas dépendre // de l'exception, puisque celle-ci est // anonyme (pas nommée) } |
try
{ Traitement (); // qui peut lever une exception CExc } catch (CExc Exc) { TraitePartielExc (); throw Exc; } |
En revanche, l'exception peut rester anonyme, comme
dans l'exemple ci-dessous :
// ...
try { Traitement (); // qui peut lever une exception CExc } catch (...) { TraitPartiel (); // ce traitement ne peut évidemment pas dépendre // de l'exception, puisque celle-ci est // anonyme (pas nommée) throw; } |
Une instruction throw; seule ne peut se trouver que dans un bloc catch.
//...
try { // ... try { Traitement (); // contient, directement ou non, "throw ExcX;" } catch (...) { TraitPartiel (); throw; } //... } catch (...) { // c'est ici qu'est passé le contrôle } |
extern void Test (paramètres_formels) throw (CExcTypeA, CExcTypeB); |
où CExcTypeA et CExcTypeB sont deux types
ou classes distinctes, en principe n'appartenant pas à la même
hiérarchie. Ainsi, l'utilisateur est-il averti explicitement et
peut-il prévoir leur traitement :
try
{ // ... Test (paramètres_effectifs); // ... } catch (const CExcTypeA & Exc) { // Traitement de l'exception de la classe CExcTypeA } catch (const CExcTypeB & Exc) { // Traitement de l'exception de la classe CExcTypeB } |
La définition de la fonction pourrait être
:
void Test (paramètres_formels) throw (CExcTypeA,
CExcTypeB)
{ // ... if (Cond_1) throw CExcTypeA (parametres_du_constructeur); // ... if (Cond_2) throw CExcTypeB (parametres_du_constructeur); // ... } // Test() |
Cependant, certains compilateurs non conformes n'autorisent
pas cette écriture. Il est alors vivement recommandé de l'indiquer
sous forme de commentaire :
extern void Test (paramètres_formels) /* throw (CExcTypeA, CExcTypeB) */; |
Une fonction qui ne comporte pas cette indication
est, par défaut, susceptible de lever n'importe quelle exception.
Cette règle permet d'assurer la compatibilité ascendante
avec les anciennes versions de C/C++ qui ne comportaient pas cette possibilité.
extern void OldTest (paramètres_formels); |
Pour être sûr de capturer une exception
provenant d'une telle fonction, il est donc nécessaire d'utiliser
le symbole "ellipse" ... :
try
{ // ... OldTest (paramètres_effectifs); // ... } catch (...) { // Traitement } |
Il est possible de garantir à l'utilisateur
qu'une fonction ne doit lever aucune exception, et il est d'ailleurs
très recommandé de le faire, de la façon suivante
:
extern void NewTestSansExcept (paramètres_formels) throw (); |
Dans ce cas, le compilateur peut vérifier :
Lorsque des exceptions sont levées dans une fonction (y compris la fonction main()) sans y être interceptées, et qui ne sont pas signalées dans la signature (le profil) de la fonction, différents traitements standard sont appliqués. Ceux-ci peuvent être modifiés par les fonctions
class CDoubleMem
{ int Size; int *p; char *q; public : CDoubleMem (const int nb) if ((p = new int [nb]) == 0) throw ExcMemInsuff, if ((q = new char [nb]) == 0) throw ExcMemInsuff; } // ... }; CDoubleMem |
Si l'allocation de la mémoire à q échoue, l'espace déjà alloué à p sera définitivement perdu..Cependant, tous les objets non dynamique (données membres ou objet de la classe mère) déjà construits sont détruits dans l'ordre inverse.
Il est possible d'intercepter des exceptions levées
dans la partie initialisation d'un constructeur (c'est-à-dire dans
l'appel des constructeurs des objets qui le constituent). La syntaxe est
illustrée par l'exemple suivant :
class CX : public CBase
{ CMembre m_Membre; public:
}; // CX CX::CX (const CBase & Base, const CMembre & Membre)
|
Il ne jamais lever une exception dans un destructeur : le desctructeur peut lui-même être appelé lorsqu'une exception est levée. Donc, dans un destructeur, il faut toujours inclure l'appel d'une fonction susceptible de lever une exception à l'intérieur d'un bloc try-catch.
Dans tous les cas, le fichier entête <exception> doit être inclus.
virtual const char* what() const throw(); |
Il est possible de lever une exception de cette classe dans un programme, mais le constructeur n'ayant aucun paramètre, il est impossible d'initialiser la chaîne de caractères. En revanche, l'utilisation de la fonction what() permet quand même d'accéder à son contenu généré le compilateur, par exemple pour l'afficher. Selon les compilateurs, il peut être assez sibyllin. Il en est de même si on dérive la classe exception en une classe personnelle, CException par exemple, sans ajouter de traitement particulier, mais le message n'est pas obligatoirement le même. Par exemple, la chaîne est initialisée à Unknown exception par Visual C++ 5.0 de Microsoft, dans les deux cas,
Contrairement à leur classe mère, les
constructeurs de toutes ces classes, à l'exclusion des trois premières,
ont pour paramètre un string, comme par exemple :
logic_error::logic_error (const string& what_arg); |
Les exceptions des deux premières classes (bad_cast et bad_typeid) sont levées lors de transtypages ou d'identification de type à l'exécution (RTTI).
Les exceptions de la classe bad_alloc sont levées lors de l'échec d'une allocation mémoire.
Les exceptions de la classe logical_error sont des exceptions correspondant à des erreurs qui pourraient être détectées lors de la compilation, comme par exemple la tentative d'accès à un élément à un rang invalide (dans un conteneur par exemple).
Les exceptions de la classe runtime_error sont des exceptions correspondant à des erreurs qui ne pourraient être détectées que lors de l'exécution du programme, comme par exemple une division par zéro.
La classe ios_base::failure correspond à des exceptions levées lors d'opérations d'entrées/sorties..
Remarque :
Certaines erreurs de calcul - division par zéro,
débordement de la mantisse d'un nombre réel devient trop
grande (positivement ou négativement), etc... - sont détectées
par la machine (hardware) et/ou par le système d'exploitation
qui en informe éventuellement l'application. Sous Unix par exemple,
le processus reçoit un signal SIGFPE par exemple (SIGnalFloating
Point
Error).
Ce mécanisme est étudié en détail lors des
TPs
de système en seconde année. Si ce signal ne subit pas
un traitement approprié, le processus est avorté, et un message
apparaît :
Floating exception |
Cependant, il ne s'agit pas d'une exception au sens du C++, et elle n'est pas récupérable par catch (...). En revanche, Visual C++ 5.0 de Microsoft reçoit bien une exception lors d'une division par 0, qui peut être capturée par catch (...).
ifstream is;
is.open ("Fichier", ios::in); if (is.fail()) ... else { int Entier; is >> Entier; if (is.bad()) ... else if (is.eof()) ... else { ... } } |
Une autre possibilité est de forcer le programme
à lever une exception de la classe ios_base::failure lorsque
le bit est positionné, au moyen de la fonction ios_base::exceptions()
de profil :
void ios_base::exceptions (iostate except); |
où except est un masque de bits de type ios_base::iostate, dans lequel peuvent être positionnés les bits correspondant à badbit, eofbit et failbit.
Le développeur n'a plus à tester individuellement
chaque opération mais peut regrouper les traitements d'erreurs en
un même point. Par exemple :
ifstream is;
is.exceptions (ios_base::badbit | ios_base::eofbit | ios_base::failbit); try { is.open ("Fichier", ios::in); int Entier; } catch () { // ... } |
void unexpected(); |
est automatiquement appelée lorsqu'une fonction reçoit une exception qui n'a pas été spécifiée dans son profil. Elle appelle elle-même le traitant (la fonction) d'exception inattendue actif à ce moment-là. Le traitant par défaut d'une exception inattendu est la fonction terminate().
La fonction peut aussi être directement appelée dans un programme.
Une fonction f, de type unexpected_handler
défini par :
typedef void (*unexpected_handler) (); |
peut être substituée au traitant courant d'exeption inatttendue
(par défaut à terminate()) au moyen de la fonction
set_unexpected()
:
unexpected_handler set_unexpected (unexpected_handler f) throw (); |
La fonction set_unexpected() renvoie un pointeur vers le précédent traitant.
La fonction unexpected() ne doit pas revenir normalement. Le traitant d'exception inattendue qui lui est associé ne doit donc pas lui rendre le contrôle. Il peut soit terminer le programme (par l'appel d'une fonction de terminaison terminate(), abort() ou exit()), soit relever une exception qui, elle, ferait partie de la signature de la fonction qui a initialement causé le déclenchement de ce mécanisme.
Par exemple :
unexpected_handler TraitantDefaut ()
{ throw CExcListe(); } // TraitantDefaut() void FctTest () throw (CExcListe)
int main (...)
set_unexpected (OldTraitantDefaut); } // main() |
Si l'exception relevée par le traitant d'exception inattendue ne fait elle-même pas partie de la signature de la fonction qui a initialement causé le déclenchement de ce mécanisme :
void FctTest () throw (CExcListe, ::bad_exception) |
et l'exception non prévue est remplacée par l'exception standard bad_exception.
bool uncaught_exception(); |
renvoie true si une exception est en cours de traitement au moment où elle est appelée. Cela permet à une fonction de modifier son comportement selon qu'elle est appelée normalement dans un programme ou au sein d'un traitement d'exception.
© D. Mathieu
mathieu@romarin.univ-aix.fr
I.U.T.d'Aix en Provence - Département Informatique