Gestion des erreurs et exceptions

Décembre 2016

Gestion des erreurs et exceptions

Les lignes de code que vous avez étudiées jusqu’à présent ne constituaient pas de vrais programmes mais des exemples. Elles ne comprenaient donc aucun traitement des erreurs. Les programmes que vous développerez devront traiter les conditions d’erreurs.

Erreurs logiques et fautes de syntaxe

Obtenir des programmes fiables et sans erreurs est la première priorité pour un programmeur sérieux.

Un programme peut contenir divers types d’erreurs. Les plus courantes sont les erreurs de logique ou de syntaxe. La première se produit lorsque vous n’avez pas analysé correctement le déroulement du programme, la seconde lorsque vous n’utilisez pas la bonne expression, fonction ou structure. Ces dernières sont facilement identifiées par le compilateur.

Le comportement du programme face à un événement inattendu est un problème plus important que les erreurs de syntaxe ou de logique : il se comporte très bien tant que l’utilisateur entre un nombre lorsqu’on le lui demande, mais il tombe en panne dès que l’utilisateur entre un chiffre. D’autres programmes vont s’interrompre s’ils manquent de mémoire, si la disquette ne se trouve pas dans le lecteur, ou si le modem n’est pas branché.

Exceptions et instructions try/catch

Vous ne pouvez pas toujours empêcher les circonstances exceptionnelles de se produire, vous devez simplement vous y préparer. La gestion des exceptions fournies par le C++ permet de traiter toute condition inhabituelle mais prévisible qui peut se produire pendant l’exécution d’un programme.

Une exception est un objet qui est transmis par la zone du code dans laquelle le problème s’est produit, vers la zone du code qui va traiter ce problème. Le type de l’exception détermine l’emplacement du code dans lequel se décide la réaction appropriée, et le contenu de l’objet qui est transmis peut être utilisé pour informer l’utilisateur.

Les blocs try permettent d’encadrer les zones de code qui pourrait créer des problèmes, comme dans l’exemple suivant :

try 
{ 
UneFonctionDangereuse(); 
}


Les blocs catch permettent ensuite de traiter les exceptions envoyées par le bloc try, comme dans l’exemple suivant :

try 
{ 
UneFonctionDangereuse(); 
} 
// Identification d’un problème de mémoire insuffisante envoyé par 
// UneFonctionDangereuse() 
catch(OutOfMemory) 
{ 
// on traite le problème de mémoire 
} 
// Identification d’un problème de fichier inconnu envoyé par 
// UneFonctionDangereuse() 
catch(FileNotFound) 
{ 
// on traite le problème de fichier inconnu 
}


Voici les étapes de base pour le traitement des exceptions :
  • Identifiez les zones du programme dans lesquelles une opération peut conduire à une situation exceptionnelle, et mettez-la dans un bloc try.


Créez des blocs catch pour récupérer les exceptions qui seront créées en cas de problème, pour libérer proprement la mémoire et pour informer l’utilisateur. Le code A.1 illustre l’utilisation de ces blocs try et catch.

Lorsqu’une exception est envoyée (ou se produit), le bloc catch qui suit le bloc try prend immédiatement le contrôle.

Vous pouvez documenter toutes les exceptions que votre fonction pourra envoyer à l’aide du mot clé throwxe "mot clé:throw dans la déclaration de cette fonction :

void UneFonctionDangereuse() throw(InExc, OutExc, AnExc);


Si la fonction crée une exception qui ne se trouve pas dans la liste, une exception unexpected() est envoyée.

À savoir

Bien que les exceptions fassent partie du standard du C++, certains anciens compilateurs ne les supportent pas. Si votre compilateur en fait partie, vous devrez installer sa version la plus récente si vous voulez compiler l’exemple de cette annexe.
Si votre compilateur fait partie de ces compilateurs récents qui ne supportent pas la spécification des exceptions dans le prototype de la fonction, supprimez cette indication throw(...).

Code A.1 : traitement d’une exception

1: #include <iostream> 
2: using namespace std; 
3: const int taille_defaut = 10; 
4: 
5: class Tableau 
6: { 
7: public: 
8:    // constructeurs 
9:    Tableau(int Taille = taille_defaut); 
10:   Tableau(const Tableau &source); 
11:   ~Tableau() { delete [] pType;} 
12: 
13:   // opérateurs 
14:   Tableau& operator=(const Tableau&); 
15:   int& operator[](int index ) throw(xLimite); 
16:   const int& operator[](int index ) throw(xLimite) const; 
17: 
18:   // méthodes d’accès 
19:   int LireTaille() const { return saTaille; } 
20: 
21:   // fonction amie 
22:  friend ostream& operator<< (ostream&, const Tableau&); 
23: 
24:   class xLimite {};  // définit la classe de l’exception 
25: private: 
26:   int *pType; 
27:   int  saTaille; 
28: }; 
29: 
30: 
31: Tableau::Tableau(int taille): 
32: saTaille(taille) 
33: { 
34:   pType = new int[taille]; 
35:   for (int i = 0; i<taille; i++) 
36:     pType[i] = 0; 
37: } 
38: 
39: 
40: Tableau& Tableau::operator=(const Tableau &source) 
41: { 
42:   if (this == &source) 
43:     return *this; 
44:   delete [] pType; 
45:   saTaille = source.LireTaille(); 
46:   pType = new int[saTaille]; 
47:   for (int i = 0; i<saTaille; i++) 
48:      pType[i] = source[i]; 
49: } 
50: 
51: Tableau::Tableau(const Tableau &source) 
52: { 
53:   saTaille = source.LireTaille(); 
54:   pType = new int[saTaille]; 
55:   for (int i = 0; i<saTaille; i++) 
56:      pType[i] = source[i]; 
57: } 
58: 
59: //Opérateurs [] surchargés 
60: int& Tableau::operator[](int index ) 
61: { 
62:   int taille = LireTaille(); 
63:   if (index  >= 0 && index  < LireTaille()) //si l’index  
64:     return pType[index];        //se trouve hors limite... 
65:   throw xLimite();              //...on envoie xLimite 
66: } 
67: 
68: 
69: const int& Tableau::operator[](int index ) const 
70: { 
71:   int maTaille = LireTaille(); 
72:   if (index  >= 0 && index  < LireTaille()) 
73:     return pType[index ]; 
74:   throw xLimite(); 
75: } 
76: //opérateur << surchargé 
77: ostream& operator<< (ostream& output, const Tableau& leTableau) 
78: { 
79:   for (int i = 0; i<leTableau.LireTaille(); i++) 
80:      output << "[" << i << "] " << leTableau[i] << endl; 
81:   return output; 
82: } 
83: 
84: void main() 
85: { 
86:   Tableau tab_entiers(5); //on crée un tableau de 5 elts 
87:   try  //on tente de stocker 100 éléments dans ce tableau 
88:   { 
89:      for (int j = 0; j< 100; j++) 
90:      { 
91:         tab_entiers[j] = j; 
92:         cout << "tab_entiers[" << j << "] OK..." << endl; 
93:      } 
94:   } 
95:  catch (Tableau::xLimite) //si exception xLimite détectée... 
96:   {                       //on affiche : 
97:      cout << "Impossible de traiter votre entrée!\n"; 
98:   } 
99:   cout << "Fin.\n"; 
99: }

Ce programme produit le résultat suivant :
tab_entiers[0] OK... 
tab_entiers[1] OK... 
tab_entiers[2] OK... 
tab_entiers[3] OK... 
tab_entiers[4] OK... 
Impossible de traiter votre entrée! 
Fin.


Il s’agit d’une classe Tableau très simple dans laquelle est déclarée une autre classe xLimite. Cette classe, qui ne contient aucune donnée, n’a aucune particularité en tant que classe d’exception. C’est une classe comme les autres à qui le compilateur attribue automatiquement un constructeur, un destructeur, un constructeur copie et l’opérateur copie (égal) par défaut. Notez que le fait de déclarer la classe exception dans Tableau n’a d’autre but que de grouper ces deux classes. Tableau n’a pas un accès particulier à xLimite, et celle-ci n’a pas un accès privilégié aux membres de Tableau.

Dans le bloc try, on essaie d’ajouter 100 entiers au tableau déclaré ligne 86. Le bloc catch qui permet de traiter les exceptions xLimite est déclaré à la ligne 95.

Lorsque j atteint la valeur de 5 (ligne 89), on accède au membre correspondant à cet index, le test de la ligne 63 échoue, et operator[] envoie une exception xLimite à la ligne 65.

L’exécution du programme se poursuit avec le bloc catch de la ligne 95, et l’exception est traitée sur la même ligne, ce qui affiche un message d’erreur. L’exécution reprend à la fin du bloc catch, à la ligne 98.

À savoir

Il est possible que plusieurs conditions provoquent une exception. Dans ce cas, on peut coder plusieurs instructions catch à la suite, d’une façon similaire aux conditions d’une instruction switch. L’équivalent de l’instruction defaut est l’instruction catch(...) qui permet de traiter tous les types d’exceptions qui n’ont pas été prévus.

Voici comment nous pourrions traiter d’autres exceptions dans le programme précédent

Créons tout d’abord quatre nouvelles classes à la suite de xLimite :

// Définition des classes d’exception 
class xLimite {}; 
class xTropGrand {}; 
class xTropPetit{}; 
class xZero {}; 
class xNegatif {};


Examinons ensuite la taille transmise au constructeur pour envoyer une exception si elle est trop grande, trop petite, négative ou nulle :

Tableau::Tableau(int taille): 
saTaille(taille) 
{ 
   if (taille == 0) 
      throw xZero(); 
   if (taille < 10) 
      throw xTropPetit(); 
   if (taille > 30000) 
      throw xTropGrand(); 
   if (taille < 1) 
      throw xNegatif(); 

   pType = new int[taille]; 
   for (int i = 0; i<taille; i++) 
     pType[i] = 0; 
}

Il reste à modifier le bloc try pour y inclure les instructions catch qui traitent toutes les conditions sauf la condition négative. Celle-ci sera traitée par l’instruction catch(...) générale :

catch (Tableau::xLimite) 
{ 
   cout <<"Impossible de traiter votre entrée!\n"; 
} 
catch (Tableau::xTropGrand) 
{ 
   cout << "Ce tableau est trop grand..." << endl; 
} 
catch (Tableau::xTropPetit) 
{ 
   cout << "Ce tableau est trop petit..." << endl; 
} 
catch (Tableau::xZero) 
{ 
  cout << "Vous avez demandé un tableau sans aucun élément !" << endl; 
} 
catch (...) 
{ 
   cout << "Il s’est produit un incident de type inconnu !" << endl; 
}


Le texte original de cette fiche pratique est extrait de
«Tout sur le C++» (Christine EBERHARDT, Collection
CommentCaMarche.net, Dunod, 2009)

A voir également :

Ce document intitulé «  Gestion des erreurs et exceptions  » issu de CommentCaMarche (www.commentcamarche.net) est mis à disposition sous les termes de la licence Creative Commons. Vous pouvez copier, modifier des copies de cette page, dans les conditions fixées par la licence, tant que cette note apparaît clairement.