Programmation C++/Les exceptions

Exceptions en C++

modifier

Une exception est l'interruption de l'exécution du programme à la suite d'un événement particulier. Le but des exceptions est de réaliser des traitements spécifiques aux événements qui en sont la cause. Ces traitements peuvent rétablir le programme dans son mode de fonctionnement normal, auquel cas son exécution reprend. Il se peut aussi que le programme se termine, si aucun traitement n'est approprié.

Le C++ supporte les exceptions logicielles, dont le but est de gérer les erreurs qui surviennent lors de l'exécution des programmes. Lorsqu'une telle erreur survient, le programme doit lancer une exception. L'exécution normale du programme s'arrête dès que l'exception est lancée, et le contrôle est passé à un gestionnaire d'exception. Lorsqu'un gestionnaire d'exception s'exécute, on dit qu'il a attrapé l'exception.

Les exceptions permettent une gestion simplifiée des erreurs, parce qu'elles en reportent le traitement. Le code peut alors être écrit sans se soucier des cas particuliers, ce qui le simplifie grandement. Les cas particuliers sont traités dans les gestionnaires d'exception.

En général, une fonction qui détecte une erreur d'exécution ne peut pas se terminer normalement. Comme son traitement n'a pas pu se dérouler normalement, il est probable que la fonction qui l'a appelée considère elle aussi qu'une erreur a eu lieu et termine son exécution. L'erreur remonte ainsi la liste des appelants de la fonction qui a généré l'erreur. Ce processus continue, de fonction en fonction, jusqu'à ce que l'erreur soit complètement gérée ou jusqu'à ce que le programme se termine (ce cas survient lorsque la fonction principale ne peut pas gérer l'erreur)[1].

Lancer une exception

modifier

Lancer une exception consiste à retourner une erreur sous la forme d'une valeur (message, code, objet exception) dont le type peut être quelconque (int, char*, MyExceptionClass, ...).

Le lancement se fait par l'instruction throw :

  throw 0;

Attraper une exception

modifier

Pour attraper une exception, il faut qu'un bloc encadre l'instruction directement, ou indirectement, dans la fonction même ou dans la fonction appelante, ou à un niveau supérieur. Dans le cas contraire, le système récupère l'exception et met fin au programme.

Les instructions try et catch sont utilisées pour attraper les exceptions.

  try {
      ... // code lançant une exception (appel de fonction, ...)
  }
  catch (int code)
  {
      cerr << "Exception " << code << endl;
  }

Exemple de fonction lançant une exception :

int division(int a, int b)
{
    if (b == 0)
    {
        throw 0; // division par zéro;
    } 
    else return a / b;
}

void main()
{
    try {
        cout << "1/0 = " << division(1, 0) << endl;
    }
    catch (int code)
    {
        cerr << "Exception " << code << endl;
    }
}

Attraper toutes les exceptions

modifier

Spécifier les points de suspension dans la clause catch permet d'attraper tous les autres types d'exception :

void main()
{
    try {
        cout << "1/0 = " << division(1, 0) << endl;
    }
    catch (int code)
    {
        cerr << "Exception " << code << endl;
    }
    catch (...)
    {
        cerr << "Exception inconnue !!!" << endl;
    }
}

Déclaration des exceptions lancées

modifier

La déclaration d'une fonction lançant un nombre limité de type d'exception, telle que la fonction division de l'exemple précédent, peut être suivie d'une liste de ces types d'exceptions dans une clause throw :

int division(int a, int b) throw(int)
{
    if (b == 0) throw 0;  // division par zéro;
    else return a / b;
}

Par défaut, la fonction peut lancer n'importe quel type d'exception. La déclaration de la fonction division sans clause throw est donc équivalent à la déclaration suivante :

int division(int a, int b) throw(...)  // n'importe quel type d'exception
{
    if (b == 0)
         throw 0;  // division par zéro;
    else
         return a / b;
}

Si une fonction ne lance aucune exception, on peut la déclarer avec une clause throw vide :

int addition(int a, int b) throw()  // aucune exception
{
    return a+b;
}

Cette clause peut être présente dans les cas suivants :

  • prototype de fonction
  • implémentation de fonction
  • pointeur de fonction

En revanche, il n'est pas possible de l'utiliser avec typedef.

Une déclaration avec clause throw limite donc les types d'exception que la fonction peut lancer. Toute tentative de lancer un autre type d'exception est détectée à l'exécution et provoque l'appel de la fonction std::terminate(), puis l'arrêt immédiat du programme. Ce comportement est spécifique au C++, et amène souvent les développeurs à remplacer cette clause par une simple documentation.

De plus, l'utilisation de la clause throw reste rare car rendant les pointeurs de fonctions incompatibles s'ils ne possèdent pas une clause couvrant les types lancés par la fonction pointée.

Il est possible de fournir sa propre fonction std::terminate() appelée en cas d'exception non attrapée, grâce à std::set_terminate().

#include <exception>
#include <iostream>

void maTerminate()
{
   std::cout << "Dans ce cas, c\'est grave !" << std::endl;
}

int main()
{
   std::set_terminate(maTerminate);   // On fournit sa propre fonction
   std::terminate();                  // Et on termine le programme.
   return 0;
}
  1. http://www.developpez.com/c/megacours/c3770.html