Signaler et gérer les situations exceptionnelles : les 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 :

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

Que se passerait-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émarrer 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.

#include <stdexcept>
using namespace std;
int factorielle(int n) {
    if ( n  < 0 ) throw invalid_argument("n doit être positif");
    if ( n == 0 ) return 1;
    return n * factorielle(n-1);
}
factorielle(4)
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.

Quelques exceptions standard

  • exception

  • runtime_error

  • invalid_argument, out_of_range, length_error

  • logic_error, bad_alloc, system_error

Exercice

Essayez de deviner le rôle de chacune des exceptions standard ci-dessus

Gestion d’exception#

Exemple 1 : ouvrir accidentellement un fichier inexistant depuis une application#

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 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 scé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.

#include <stdexcept>
using namespace std;
int factorielle(int n) {
    if ( n  < 0 ) throw invalid_argument("n doit être positif");
    if ( n == 0 ) return 1;
    return n * factorielle(n-1);
}
#include <iostream>
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.

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’instructions est exécuté

Résumé#

  • Nous avons vu comment signaler une situation 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.