Programmation C-C++/Les exceptions/Lancement et récupération d'une exception

Un livre de Wikilivres.
Cours de C/C++
^
Les exceptions
Lancement et récupération d'une exception
Remontée des exceptions
Liste des exceptions autorisées pour une fonction
Hiérarchie des exceptions
Exceptions dans les constructeurs

Livre original de C. Casteyde

En C++, lorsqu'il faut lancer une exception, on doit créer un objet dont la classe caractérise cette exception, et utiliser le mot clé throw. Sa syntaxe est la suivante :

throw objet;

où objet est l'objet correspondant à l'exception. Cet objet peut être de n'importe quel type, et pourra ainsi caractériser pleinement l'exception.

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 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
}

Notez que les objets de classe de stockage automatique définis dans le bloc try sont automatiquement détruits lorsqu'une exception fait sortir le contrôle du programme de leur portée. C'est également le cas de l'objet construit pour lancer l'exception. Le compilateur effectue donc une copie de cet objet pour le transférer au premier bloc catch capable de le recevoir. Cela implique qu'il y ait un constructeur de copie pour les classes d'exceptions non triviales.

De même, les blocs catch peuvent recevoir leurs paramètres par valeur ou par référence, comme le montre la syntaxe indiquée ci-dessus. En général, il est préférable d'utiliser une référence, afin d'éviter une nouvelle copie de l'objet de l'exception pour le bloc catch. Toutefois, on prendra garde au fait que dans ce cas, les modifications effectuées sur le paramètre seront effectuées dans la copie de travail du compilateur et seront donc également visibles dans les blocs catch des fonctions appelantes ou de portée supérieure, si l'exception est relancée après traitement.

Il peut y avoir plusieurs gestionnaires d'exceptions. Chacun traitera les exceptions qui ont été générées dans le bloc try et dont l'objet est de la classe indiquée par son paramètre. Il n'est pas nécessaire de donner un nom à l'objet (temp) dans l'expression catch. Cependant, cela permet de le récupérer, ce qui peut être nécessaire si l'on doit récupérer des informations sur la nature de l'erreur.

Enfin, il est possible de définir un gestionnaire d'exception universel, qui récupérera toutes les exceptions possibles, quels que soient leurs types. Ce gestionnaire d'exception doit prendre comme paramètre trois points de suspension entre parenthèses dans sa clause catch. Bien entendu, dans ce cas, il est impossible de spécifier une variable qui contient l'exception, puisque son type est indéfini.

Exemple 9-1. Utilisation des exceptions[modifier | modifier le wikicode]

#include <iostream>

using namespace std;

class erreur   // Première exception possible, associée
               // à l'objet erreur.
{
public:
    int cause;  // Entier spécifiant la cause de l'exception.
    // Le constructeur. Il appelle le constructeur de cause.
    erreur(int c) : cause(c) {}
    // Le constructeur de copie. Il est utilisé par le mécanisme
    // des exceptions :
    erreur(const erreur &source) : cause(source.cause) {}
};

class other {};   // Objet correspondant à toutes
                  // les autres exceptions.

int main(void)
{
    int i;            // Type de l'exception à générer.
    cout << "Tapez 0 pour générer une exception Erreur, "
        "1 pour une Entière :";
    cin >> i;         // On va générer une des trois exceptions
                      // possibles.
    cout << endl;
    try               // Bloc où les exceptions sont prises en charge.
    {
        switch (i)    // Selon le type d'exception désirée,
        {
        case 0:
            {
                erreur a(0);
                throw (a);   // on lance l'objet correspondant
                             // (ici, de classe erreur).
                             // Cela interrompt le code. break est
                             // donc inutile ici.
            }
        case 1:
            {
                int a=1;
                throw (a);   // Exception de type entier.
            }
        default:             // Si l'utilisateur n'a pas tapé 0 ou 1,
            {
                other c;     // on crée l'objet c (type d'exception
                throw (c);   // other) et on le lance.
            }
        }
    }                 // fin du bloc try. Les blocs catch suivent :
    catch (erreur &tmp) // Traitement de l'exception erreur ...
    {                 // (avec récupération de la cause).
        cout << "Erreur erreur ! (cause " << tmp.cause << ")" << endl;
    }
    catch (int tmp)   // Traitement de l'exception int...
    {
        cout << "Erreur int ! (cause " << tmp << ")" << endl;
    }
    catch (...)       // Traitement de toutes les autres
    {                 // exceptions (...).
                      // On ne peut pas récupérer l'objet ici.
        cout << "Exception inattendue !" << endl;
    }
    return 0;
}

Selon ce qu'entre l'utilisateur, une exception du type erreur, int ou other est générée.