Traitement des erreurs et exceptions

Occasionnellement, un programme, ou plus généralement une fonction, peut rencontrer une situation exceptionnelle: c'est-à-dire une situation qui n'est pas prévue dans les entrées normales. Que faire dans ce cas? Continuer comme si de rien était pourrait amener toutes sortes de catastrophes. Renvoyer une valeur arbitraire risquerait de passer inaperçu par l'utilisateur du programme ou de la fonction.

On souhaite donc arrêter l'exécution de la fonction, en signalant qu'une situation exceptionnelle s'est produite.

Exemple: gestion d’entrées invalides

Nous avons vu précédemment la fonction factorielle récursive:

In [ ]:
int factorielle(int n) {
    if ( n == 0 ) return 1;
    return n * factorielle(n-1);
}
In [ ]:
factorielle(4)

Que se passerez-t'il si on calculait factorielle(-1)? Cela appellerait factorielle(-2) qui à son tour appellerait factorielle(-3), et ainsi de suite. Jusqu'à ce que le programme plante. Allez-y, essayez pour voir ci-dessus. Et tenez vous prêt à redémarer le noyau!

L'entrée -1 n'est pas prévue par la fonction. C'est une situation exceptionnelle que l'on souhaiterait signaler immédiatement et de façon plus informative que par un simple plantage.

Note: il vous faudra probablement redémarer le noyau avant de redéfinir factorielle comme ci-dessous

In [ ]:
#include <stdexcept>
using namespace std;
In [ ]:
int factorielle(int n) {
    if ( n  < 0 ) throw invalid_argument("n doit être positif");
    if ( n == 0 ) return 1;
    return n * factorielle(n-1);
}
In [ ]:
factorielle(4)
In [ ]:
factorielle(-1)

Maintenant, l'utilisateur est satisfait: l'exécution de factorielle s'est immédiatement arrêté, avec un message d'erreur explicite sur le problème rencontré.

Nous allons maintenant expliquer les différents éléments utilisés: throw (lancer), invalid_argument et la bibliothèque stdexcept.

Signaler une exception

Syntaxe:

  throw e;

Sémantique:

  • Une situation exceptionnelle que je ne sais pas gérer s’est produite
  • Je m’arrête immédiatement et je préviens mon «chef» (l'utilisateur ou la fonction appelante)
  • On dit qu’on signale une exception
  • La situation est décrite par l'exception e
    e est un objet quelconque; par exemple une exception standard
  • Si à son tour mon «chef» ne sait pas gérer, il prévient son «chef»
  • Si personne ne sait gérer, le programme s’arrête

Quelques exceptions standard

Les erreurs dans les programmes peuvent être classifiée en plusieurs types classiques; ci-dessus, il s'agissait d'un «argument invalide». Dans d'autre cas, cela peut être un problème d'allocation, une erreur arithmétique, etc. Cette classification aide le lecteur et l'utilisateur à mieux analyser les erreurs et, par exemple, décider du comportement à adopter face à l'une d'elle.

Pour cela, la bibliothèque standard stdexcept fournit plusieurs types d'exceptions standard qui forment une hiérarchie avec, tout en haut, le type le moins spécifique exception; en voici quelques unes:

  • exception
  • runtime_error
  • invalid_argument, out_of_range, length_error
  • logic_error, bad_alloc, system_error
  • ...

Essayez de deviner le rôle de chacune.

Gestion d’exception

Exemple 1

Imaginez que vous utilisiez votre traitement de texte, et que vous lui demandiez d'ouvrir un fichier qui n'existe pas. Pour la fonction en charge d'ouvrir ce fichier, c'est une situation exceptionnelle. Pour autant, serait-il acceptable que le traitement de texte plante avec juste un petit message «fichier inexistant»?

Non: pour un traitement de texte, ce n'est pas une situation exceptionnelle. Il sait gérer: prévenir l'utilisateur que le fichier n'existe pas, puis continuer normalement.

Une situation peut-être exceptionnelle pour une fonction, sans qu'elle le soit pour pour son «chef».

Il nous faut donc un mécanisme par lequel le chef puisse prendre la main et gérer la situation.

Exemple 2

Nous allons considérer un sénario similaire, en plus simple: un programme qui demande un nombre $n$ à l'utilisateur et affiche la factorielle de ce nombre. Pour ce programme, ce n'est pas exceptionnel que l'utilisateur fasse des bêtises en saisissant une valeur invalide. Le programme sait gérer, en affichant un petit message avant de continuer.

Voilà comment cela s'écrit. Pour que tout soit au même endroit, nous rappelons d'abord la définition de la fonction factorielle.

In [ ]:
#include <stdexcept>
using namespace std;
In [ ]:
int factorielle(int n) {
    if ( n  < 0 ) throw invalid_argument("n doit être positif");
    if ( n == 0 ) return 1;
    return n * factorielle(n-1);
}
In [ ]:
#include <iostream>
In [ ]:
int n;
cout << "Veuillez saisir un nombre entier positif:" << endl;
cin >> n;
try {
    int f = factorielle(n);
    cout << "Factorielle n vaut " << f << endl;
} catch (invalid_argument & e) {
    cout << "Valeur de n invalide: " << n << endl;
}
cout << "Je continue ..." << endl;

Les deux éléments nouveaux sont try (essayer) et catch (rattraper l'erreur). En voici la signification.

$\clubsuit$ Gestion des exceptions

Syntaxe:

try {
        bloc d´instructions;
    } catch (type & e) {
        bloc d´instructions;
    }

Sémantique:

  • Exécute le premier bloc d’instructions
  • Si le bloc signale une exception de type type, ce n’est pas grave, je sais gérer:
    • L’exécution du premier bloc d’instruction s’interrompt
    • Le deuxième bloc d’instruction est exécuté

Résumé

Nous avons vu comment signaler une situations exceptionnelle (communément appellée erreur) lors de l'exécution d'un programme ou d'une fonction.

Cela se fait avec throw ee est une exception. Cette dernière est typiquement construite avec l'un des types d'exceptions de la bibliothèque standard stdexcept.

Les fonctions appelantes peuvent alors gérer cette exception, à l'aide de try ... catch .... Si aucune d'entre elles ne gère l'exception, alors le programme s'arrête.

Il y aurait bien d'autres choses à dire sur les exceptions, mais cela sera suffisant pour notre usage ce semestre.