La gestion d'exceptions en C++ : déclaration, utilisation et personnalisation
Les erreurs peuvent être classées en deux grandes catégories :
- Erreurs de compilation – Les erreurs détectées pendant la compilation sont appelées erreurs de compilation. Les erreurs de compilation incluent une référence de bibliothèque, une erreur de syntaxe ou une importation de classe incorrecte.
- Erreurs d'exécution - Elles sont également appelées exceptions. Une exception interceptée pendant l'exécution crée de sérieux problèmes.
Une exception est un problème qui survient lors de l'exécution d'un programme. Une exception C++ est une réponse à une circonstance exceptionnelle (généralement une erreur) qui se produit pendant l'exécution d'un programme, telle qu'une tentative de division par zéro.
Toutes les exceptions sont dérivées de la classe std::exception. Il s'agit d'une erreur d'exécution qui peut être gérée. Si nous ne gérons pas l'exception, il affiche le message d'exception et termine le programme.
Pourquoi la gestion des exceptions ?
Voici les principaux avantages de la gestion des exceptions par rapport à la gestion des erreurs traditionnelle.
- Séparation du code de gestion des erreurs du code normal : dans les codes de gestion des erreurs traditionnels, il existe toujours des conditions if-else pour gérer les erreurs. Ces conditions et le code pour gérer les erreurs sont mélangés avec le flux normal. Cela rend le code moins lisible et maintenable. Avec la gestion d'erreurs, le code de gestion des erreurs est séparé du flux normal.
- Les fonctions/méthodes peuvent gérer toutes les exceptions de leur choix : une fonction peut lever de nombreuses exceptions, mais peut choisir de gérer certaines d'entre elles. Les autres exceptions qui sont levées mais non interceptées peuvent être gérées par l'appelant. Si l'appelant choisit de ne pas les intercepter, les exceptions sont gérées par l'appelant de l'appelant. Nous appelons cette stratégie la hiérarchie des appelants (hiérarchie des appels de fonctions).
- Regroupement des types d'erreur : en C++, les types de base et les objets peuvent être levés en tant qu'exceptions. Nous pouvons créer une hiérarchie d'objets d'exception, regrouper les exceptions dans des espaces de noms ou des classes, les catégoriser selon les types.
Gestion des exceptions
La gestion des exceptions C++ repose sur trois mots-clés : try, catch et throw.
- L'instruction try vous permet de définir un bloc de code à tester pour les erreurs pendant son exécution.
- L'instruction catch vous permet de définir un bloc de code à exécuter si une erreur se produit dans le bloc try.
- Le mot-clé throw lève une exception lorsqu'un problème est détecté, ce qui nous permet de créer une erreur personnalisée.
En supposant qu'un bloc lève une exception, une méthode intercepte une exception en utilisant une combinaison des mots-clés try et catch. Un bloc try/catch est placé autour du code qui peut générer une exception. Le code dans un bloc try/catch est appelé code protégé, et la syntaxe pour utiliser try/catch est la suivante :
try { // placer ci le code protégé } catch( NomException e1 ) { // gérer l'exception e1 } catch( NomException e2 ) { // gérer l'exception e2 } catch( NomException eN ) { // gérer l'exception eN }
Vous pouvez répertorier plusieurs instructions catch pour intercepter différents types d'exceptions au cas où votre bloc try lèverait plus d'une exception dans différentes situations.
Lever des exceptions
Les exceptions peuvent être levées n'importe où dans un bloc de code à l'aide de l'instruction throw. L'opérande de l'instruction throw détermine le type d'exception levée.
Exemple 1 : La fonction ci-dessous lève une exception lorsque la division par zéro se produit.
#include <iostream> using namespace std; double division(int a, int b) { if( b == 0 ) { throw "Division par zéro!"; } return (a/b); } int main() { cout<< division(5,0)<< endl; return 0; }
terminate called after throwing an instance of 'char const*'
Intercepter une exception
Le bloc catch qui suit le bloc try intercepte toute exception. Vous pouvez spécifier le type d'exception que vous souhaitez intercepter et cela est déterminé par la déclaration d'exception qui apparaît entre parenthèses après le mot-clé catch.
try { // code protégé } catch( NomException e ) { // code pour gérer l'exception NomException }
Le code ci-dessus interceptera une exception de type NomException. Si vous souhaitez spécifier qu'un bloc catch doit gérer tout type d'exception qui est levée dans un bloc try, vous devez mettre des points de suspension, ..., entre les parenthèses entourant la déclaration d'exception comme suit
try { // code protégé } catch( ... ) { // code pour gérer toute exception }
#include <iostream> using namespace std; double division(int a, int b) { if( b == 0 ) { throw "Division par zero!"; } return (a/b); } int main() { int a=5,b=0,res; try{ res=division(a,b); cout<<"résultat de a/b = "<<endl; }catch(const char* msg){ cerr << msg << endl; } cout<<"bye bye"; return 0; }
Parce que nous levons une exception de type const char*, donc pour intercepter cette exception, nous devons utiliser const char* dans le bloc catch.
Division par zero! bye bye
Si une exception est levée et n'est interceptée nulle part, le programme se termine anormalement. Par exemple, dans le programme suivant, un const char* est levé, mais il n'y a pas de bloc catch pour intércepter un const char*.
#include <iostream> using namespace std; double division(int a, int b) { if( b == 0 ) { throw "Division par zéro!"; } return (a/b); } int main() { int a=5,b=0,res; try{ res=division(a,b); cout<<"résultat de a/b = "<<endl; }catch(int msg){ cerr << msg << endl; } cout<<"bye bye"; return 0; }
terminate called after throwing an instance of 'char const*'
Exceptions C++ standard
C++ fournit une liste d'exceptions standard définies dans std:exception que nous pouvons utiliser dans nos programmes. Ceux-ci sont organisés dans une hiérarchie de classes parent-enfant illustrée ci-dessous :
- std::exception - Une exception et une classe parente de toutes les exceptions C++ standard.
- std::bad_alloc - Cela peut être lancé par new.
- std::bad_cast - Cela peut être lancé par dynamic_cast.
- std::bad_exception - Utile pour gérer les exceptions inattendues dans un programme C++.
- std::bad_typeid - Cela peut être lancé par typeid.
- std::logic_error - Une exception qui peut théoriquement être détectée en lisant le code.
- std::domain_error - Il s'agit d'une exception levée lorsqu'un domaine mathématiquement invalide est utilisé.
- std::invalid_argument - Ceci est renvoyé en raison d'arguments invalides.
- std::length_error - Ceci est levé lorsqu'un std::string trop gros est créé.
- std::out_of_range - Cela peut être renvoyé par la méthode 'std::string::at() ' car il y a un passage d'un index incorrect.
- std::runtime_error - Une exception qui théoriquement ne peut pas être détectée en lisant le code.
- std::overflow_error - Ceci est levé si un débordement mathématique se produit.
- std::range_error - Cela se produit lorsque vous essayez de stocker une valeur hors plage d'indices.
- std::underflow_error - Ceci est levé si un débordement mathématique se produit.
Exceptions définies par l'utilisateur
Vous pouvez définir vos propres exceptions en héritant et en remplaçant (surcharger) la fonctionnalité de classe std::exception. Voici un exemple, qui montre comment vous pouvez utiliser la classe std::exception pour implémenter votre propre exception de manière standard :
#include <iostream> #include <exception> using namespace std; class DivisionError : public exception{ public: const char * what() const throw() { return "Division par zero!\n"; } }; double division(int a, int b) { if( b == 0 ) { throw DivisionError(); } return (a/b); } int main() { int a=5,b=0,res; try{ res=division(a,b); cout<<"résultat de a/b = "<<endl; }catch(DivisionError& e){ cerr << e.what() << endl; } cout<<"bye bye"; return 0; }
Division par zero! bye bye
Dans l'exemple ci-dessus, what() est une méthode publique fournie par la classe d'exception. Elle est utilisée pour renvoyer la cause d'une exception.