9.3. Liste des exceptions autorisées pour une fonction

Il est possible de spécifier les exceptions qui peuvent être lancées par une fonction. Pour cela, il faut faire suivre son en-tête du mot clé throw avec, entre parenthèses et séparées par des virgules, les classes des exceptions qu'elle est autorisée à lancer. Par exemple, la fonction suivante :

int fonction_sensible(void)
throw (int, double, erreur)
{
    ...
}

n'a le droit de lancer que des exceptions du type int, double ou erreur. Si une exception d'un autre type est lancée, par exemple une exception du type char *, il se produit encore une fois une erreur à l'exécution.

En fait, la fonction std::unexpected est appelée. Cette fonction se comporte de manière similaire à std::terminate, puisqu'elle appelle par défaut une fonction de traitement de l'erreur qui elle-même appelle la fonction std::terminate (et donc abort en fin de compte). Cela conduit à la terminaison du programme. On peut encore une fois changer ce comportement par défaut en remplaçant la fonction appelée par std::unexpected par une autre fonction à l'aide de std::set_unexpected, qui est déclarée dans le fichier d'en-tête exception. Cette dernière attend en paramètre un pointeur sur la fonction de traitement d'erreur, qui ne doit prendre aucun paramètre et qui ne doit rien renvoyer. std::set_unexpected renvoie le pointeur sur la fonction de traitement d'erreur précédemment appelée par std::unexpected.

Note : Comme leurs noms l'indiquent, std::unexpected et std::set_unexpected sont déclarées dans l'espace de nommage std::, qui est réservé pour les objets de la librairie standard C++. Si vous ne voulez pas avoir à utiliser systématiquement le préfixe std:: pour ces noms, vous devrez ajouter la ligne « using namespace std; » après avoir inclus l'en-tête exception. Vous obtiendrez de plus amples renseignements sur les espaces de nommage dans le Chapitre 11.

Il est possible de relancer une autre exception à l'intérieur de la fonction de traitement d'erreur. Si cette exception satisfait la liste des exceptions autorisées, le programme reprend son cours normalement dans le gestionnaire correspondant. C'est généralement ce que l'on cherche à faire. Le gestionnaire peut également lancer une exception de type std::bad_exception, déclarée comme suit dans le fichier d'en-tête exception :

class bad_exception : public exception
{
public:
    bad_exception(void) throw();
    bad_exception(const bad_exception &) throw();
    bad_exception &operator=(const bad_exception &) throw();
    virtual ~bad_exception(void) throw();
    virtual const char *what(void) const throw();
};
Cela a pour conséquence de terminer le programme.

Enfin, le gestionnaire d'exceptions non autorisées peut directement mettre fin à l'exécution du programme en appelant std::terminate. C'est le comportement utilisé par la fonction std::unexpected définie par défaut.

Exemple 9-3. Gestion de la liste des exceptions autorisées

#include <iostream>
#include <exception>

using namespace std;

void mon_gestionnaire(void)
{
    cout << "Une exception illégale a été lancée." << endl;
    cout << "Je relance une exception de type int." << endl;
    throw 2;
}

int f(void) throw (int)
{
    throw "5.35";
}

int main(void)
{
    set_unexpected(&mon_gestionnaire);
    try
    {
        f();
    }
    catch (int i)
    {
        cout << "Exception de type int reçue : " <<
           i << endl;
    }
    return 0;
}

Note : La liste des exceptions autorisées dans une fonction ne fait pas partie de sa signature. Elle n'intervient donc pas dans les mécanismes de surcharge des fonctions. De plus, elle doit se placer après le mot clé const dans les déclarations de fonctions membres const (en revanche, elle doit se placer avant =0 dans les déclarations des fonctions virtuelles pures).

On prendra garde au fait que les exceptions ne sont pas générées par le mécanisme de gestion des erreurs du C++ (ni du C). Cela signifie que pour avoir une exception, il faut la lancer, le compilateur ne fera pas les tests pour vous (tests de débordements numériques dans les calculs par exemple). Cela supposerait de prédéfinir un ensemble de classes pour les erreurs génériques. Les tests de validité d'une opération doivent donc être faits malgré tout et, le cas échéant, il faut lancer une exception pour reporter le traitement en cas d'échec. De même, les exceptions générées par la machine hôte du programme ne sont en général pas récupérées par les implémentations et, si elles le sont, les programmes qui les utilisent ne sont pas portables.