VII Le traitement des erreurs par exceptions

Les exceptions fournissent un mécanisme de gestion des erreurs structuré et rigoureux ... au prix d'un certain coup en temps d'exécution. Commençons par étudier les lacunes des systèmes classiques de gestion des erreurs.

Plan du chapitre :

  1. Les anciens mécanismes de gestion des erreurs
    1. Emission de messages
    2. Fonction renvoyant un statut
  2. Les exceptions
    1. Le type des exceptions
    2. La mise en place des exceptions sur un exemple
    3. Lancer une exception
    4. Gérer les exceptions
    5. Propagation des exceptions
    6. Plus d'informations dans les exceptions
    7. Achevons notre exemple !

Liste des programmes

  1. Exemple de code de gestion d'erreurs basé message
  2. Exemple de gestion d'erreur par retour de fonction
  3. La déclaration de la classe std::exception
  4. Déclaration des classes d'exceptions
  5. Lancement des exceptions
  6. Bloc de code surveillé
  7. Structure d'un gestionnaire d'exception type
  8. Gestion d'exceptions multiples
  9. Exemple de fonction terminate
  10. Mise en place de fonction terminate
  11. Remontée d'une exception
  12. Exemple de fonction unexpected
  13. Installation d'une fonction unexpected
  14. Des exceptions qui contiennent de l'information
  15. Version finale de la classe et de ses exceptions
  16. Exemple d'utilisation de la version finale

Liste des figures

  1. Schéma d'imbrication de classes

7.1 Les anciens mécanismes de gestion des erreurs

Au cours de l'histoire du génie logiciel, plusieurs mécanismes ont été proposés pour permettre de gérer les erreurs.

7.1.1 Emission de messages

Ce mécanisme propose d'émettre un message à destination de l'utilisateur lorsque la condition d'erreur est détectée. Une fois le message émis, l'exécution peut se poursuivre si l'erreur est jugée bégnine ou bien le programme peut être arrété.

Typiquement, vous obtenez un code du genre :

if (condition_erreur)
{
  cerr << "Attention a la grosse erreur " << endl;
  exit(1); // Si erreur fatale !
}

Programme 7.1 Exemple de code de gestion d'erreurs basé message

Avantage
Inconvénients

Une variante courante de réaliser ceci consiste à utiliser la macro assert (assert.h) de prototype :

void assert( int expression );

Lorsque l'expression en argument est nulle, assert imprime un message d'erreur et appelle abort. Le gros problème de cette dernière est qu'elle provoque la mort du programme de manière brutale (en appelant le signal SIG_ABRT sous Unix) c'est à dire sans fermer de fichier ou nettoyer la mémoire ni appeler les fonctions de terminaison que vous pourriez avoir installé (en particulier, à l'aide de atexit).

Toutefois, le principal attrait de la macro assert est de pouvoir être désactivé en définissant le symbôle NDEBUG. Vous avez 2 manières de faire ça :

7.1.2 Fonction renvoyant un statut

C'est le mécanisme le plus utilisé à l'heure actuelle. La valeur retournée par les fonctions d'une bibliothèque correspond à une valeur d'erreur. Par exemple, la plupart des fonctions de l'API Windows retournent une valeur de type HRESULT que vous devez ensuite décoder et comparer à l'ensemble des constantes présentes dans le fichier winerror.h (il y en a près de 1500).

Avantages
Inconvénients

Voici un modèle de code de mise en place et utilisation de codes de retour :

Fichier de déclaration des constantes (notez les gros #define porkasses) Exemple de fonction qui utilise ce formalisme
#ifndef __MES_CONSTANTES_D_ERREUR__
#define __MES_CONSTANTES_D_ERREUR__


typedef unsigned long TYPE_ERREUR;


#define CONSTANTE_SUCCES  0


#define CONSTANTE_ERREUR1 1
#define CONSTANTE_ERREUR2 2


#endif
 
TYPE_ERREUR fonction(parametres formels)
{
  if (ConditionErreur1)
    return CONSTANTE_ERREUR1;
  if (ConditionErreur2)
    return CONSTANTE_ERREUR2;
  …
  return CONSTANTE_SUCCES;
}
 
Appel de la fonction précédente avec gestion des erreurs


switch (fonction(parametres effectifs))
{
  case CONSTANTE_ERREUR1:
    …
    break;
  case CONSTANTE_ERREUR2:
    …
   break;
  case SUCCES:
   …
   break;
}

Programme 7.2 Exemple de gestion d'erreur par retour de fonction

Vous allez dire que j'insiste lourdement, mais le principal problème tient au fait qu'il est possible de ne pas tenir compte du code de l'erreur.

Ce système présente plusieurs variantes :

7.2 Les exceptions

Les exceptions fournissent une réponse à la plupart des problèmes posés par la gestion des erreurs, au sens où :

7.2.1 Le type des exceptions

D'après la norme, tout type de donnée, fusse même un simple entier, peut être utilisé en tant qu'exception. Ce qui permet, en particulier, de réutiliser les bonnes vieilles constantes ! Toutefois, on utilise le plus souvent des classes.

Afin d'éviter que les noms des classes d'exceptions ne se télescopent (on ne compte plus le nombre de classes nommées Erreur), on les définit le plus souvent comme des classes imbriquées. C'est à dire que l'on va déclarer une classe à l'intérieur d'une autre selon le schéma suivant :

Schéma de classes imbriquées

Figure 7.1 Les classes imbriquées

Attention ! les règles de visibilité entre classes imbriquées et classe imbriquantes sont les mêmes que pour des classes sans aucun lien.

Notez que la librairie standard du C++ prévoit toute une hiérarchie de classes d'exceptions dont le plus intéressante est la classe de base std::exception dont voici les spécifications :

class exception 
{
  public:
    exception() throw();
    exception(const exception& rhs) throw();
    exception& operator=(const exception& rhs) throw();
    virtual ~exception() throw();
    virtual const char *what() const throw();
};

Programme 7.3 La déclaration de la classe std::exception

Pour l'instant, ne prétez pas attention aux directives throw() qui vous seront expliquées plus tard. Le plus intéressant est la méthode virtuelle what qui permet d'envoyer un message à l'utilisateur. Aussi, lorsque l'on dérive une classe de std::exception, l'on a tout intérêt à redéfinir cette méthode

Comme nous le verrons par la suite, il est possible de traiter les exceptions par hiérarchie. Aussi, je vous recommande de toujours faire dériver vos classes d'exceptions de std::exception.

7.2.2 La mise en place des exceptions sur un exemple

Reprenons notre classe Chaine. Beaucoup d'erreurs peuvent se produire, parmi celles-ci, nous allons nous focaliser sur :

La déclaration des classes d'erreurs peut se faire ainsi :

#include <exception>
using namespace std;




class Chaine
{
 
  public:
  
  class Erreur : public exception
  {
    public:
      virtual const char * what(void) const throw ()
      {
        return "Erreur generique sur les chaines";
      }
  };
  
  class ErreurAllocation : public Erreur
  {
    public:
      virtual const char * what(void) const throw ()
      {
        return "Erreur d'allocation sur une chaine";
      }
  };
  
  class ErreurAcces : public Erreur
  {
    public:
      virtual const char *what(void) const throw()
      {
        return "Erreur d'acces sur une chaine avec l'operateur []";
      }
  };
  
  class ErreurAccesInferieur : public ErreurAcces
  {
    public:
      virtual const char *what(void) const throw()
      {
        return "Erreur d'acces sur une chaine avec l'operateur [] et avec indice negatif";
      }      
  };
  
  class ErreurAccesSuperieur : public ErreurAcces
  {
    public:
      virtual const char *what(void) const throw()
      {
        return "Erreur d'acces sur une chaine avec l'operateur [] et avec indice au dela de la capacite";
      }      
  };

Programme 7.4 Déclaration des classes d'exceptions

7.2.3 Lancer une exception

Les exceptions se lancent avec la directive throw. Attention ! on ne lance pas un type mais un objet. Par exemple, si vous voulez lancer une exception de type ErreurAllocation, vous utilisez throw ErreurAllocation(), ce qui revient à appeler le constructeur « à la volée » comme une fonction qui renverrait un objet.

Par exemple, si nous voulons lancer des exceptions, nous pourrions réécrire certaines méthodes de la classe chaine ainsi :

  // Constructeur par defaut
  Chaine(int taille=16) :
    longueur_(0),
    capacite_(taille)
  {
    if (taille)
    {
      tableau=new char [taille];
      if (!tableau)
        throw ErreurAllocation();
    }
    else
      tableau=0;
  }
  
  // Constructeur prenant un pointeur sur char 
  Chaine(const char *pStr)
  {
    if (pStr)
    {
      int taille=strlen(pStr);
      longueur_=capacite_=taille;
      if (taille)
      {
        tableau = new char [taille+1];
        if (tableau == 0)
            throw ErreurAllocation();
        strcpy(tableau,pStr);
      }
      else
        tableau=0;      
    }
    else
    {
      longueur_=0;
      capacite_=0;
      tableau=0;
    }
  }


  const char operator[](int index) const  
  {
    if (index < 0) 

      throw ErreurAccesInferieur();
    if (index >= longueur)
      throw ErreurAccesSuperieur(); 
    return tableau[index];
  }
  
  char &operator[](int index) 
  {


    if (index < 0) 
      throw ErreurAccesInferieur();
    if (index >= longueur)
      throw ErreurAccesSuperieur();
    return tableau[index];
  }
  
    
    
  void clonage(const Chaine &uneChaine)
  {
    longueur_=uneChaine.longueur_;
    capacite_=uneChaine.capacite_;
    tableau = new char [capacite_+1];
    if (tableau == 0)
      throw ErreurAllocation();
    if (longueur_)
      strcpy(tableau,uneChaine.tableau);
  }

Programme 7.5 Lancement des exceptions

Vous voyez ! c'est pas compliqué !

7.2.4 Gérer les exceptions

Bon, maintenant faut bien écrire du code pour les gérer les exceptions ! La première chose à faire consiste à définir des blocs protégés. Pour ce faire, on fait précéder du mot try et on ensère entre accolades les sections de code où des exceptions sont susceptibles d'être levées. Par exemple :

int main(int, char **)
{
  try
  {
    Chaine uneChaine("toto");


    Chaine uneSecondeChaine(INT_MAX);


    cout << uneChaine[3] << endl;
    cout << uneChaine[5] << endl;
    cout << uneChaine[-1] << endl;
  }
}

Programme 7.6 Bloc de code surveillé

Vous devinez ici pourquoi la gestion des exceptions est un mécanisme lourd : le compilateur rajoute des instructions qui sont à l'écoute des exceptions levées. Le code est plus lent à exécuter mais plus sur ...

Ce code porte en lui 3 erreurs potentielles :

  1. Une allocation impossible à réaliser (INT_MAX char)
  2. Deux accès au dela des limites dans une chaîne un supérieur et un inférieur

Pour les gérer, nous allons rajouter des gestionnaires d'exceptions à la suite du bloc try. Chaque gestionnaire est de la forme :

catch (ClasseException &objet)
{
  // code de traitement
}

Programme 7.7 Structure d'un gestionnaire d'exception type

Notez que le gestionnaire reçoit un objet dans le but de pouvoir appeler des méthodes dessus ! Lorsqu'une erreur est détectée dans le bloc try le pointeur d'exécution passe aux gestionnaires qu'il examine séquentiellement. Dès qu'un gestionnaire adéquat est trouvé, son code est exécuté. Le pointeur d'exécution passe ensuite au code suivant le dernier gestionnaire. L'exécution ne reprend jamais après l'instruction fautive.

Notez que les objets sont passés par référence afin d'éviter un appel au constructeur de recopie. Il y a une autre raison : en passant une référence, vous êtes polymorphiques. Ainsi le gestionnaire catch (Chaine::Erreur &e) est capable de gérer non seulement toutes les exceptions de type Chaine::Erreur mais également toutes ses classes dérivées. A ce sujet, un avertissement solennel s'impose donc :

Toujours placer les gestionnaires d'exceptions spécialisées avant le gestionnaire d'exception générale

Donc, si vous souhaitez récupérer individuellement toutes les exceptions de la classe Chaine, vous pouvez faire :

try
{
  // code truffe de betises !
}


catch (Chaine::ErreurAccesInferieur &e)
{
  cerr << e.what() << endl;
  // Traitement specifique a cette erreur
}


catch (Chaine::ErreurAccesSuperieur &e)
{
  cerr << e.what() << endl;
  // Traitement specifique a cette erreur
}


catch (Chaine::ErreurAllocation &e)
{
  cerr << e.what() << endl;
  // Traitement specifique a cette erreur
}

Programme 7.8 Gestion d'exceptions multiples

Il est neanmoins bon de rajouter 3 autres traitements :

Chaine::Erreur
Des fois que vous rajoutiez de nouvelles erreurs dans la classe Chaine mais que vous ne vouliez pas changer le code client.
std::exception
qui permet de récupérer toutes les exceptions filles de std::exception. Si vous souhaitez simplement afficher le message d'erreur, cela suffit amplement !
...
C'est le gestionnaire universel, aussi connu comme le ramasse merde. Avec ceci, vous pouvez récupérer toute exception, mais vous n'avez aucune indication sur la nature de l'exception sauf si vous utilisez les informations de type à l'exécution.

7.2.5 Propagation des exceptions

Question : que se passe t'il si vous ne gérez pas une exception ?

C'est très simple : elle remonte le long de la pile d'appel d'appelant en appelant jusqu'à arriver, éventuellement, à main. Si elle n'est toujours pas gérée à ce moment là, elle appelle la fonction terminate qui comme son nom l'indique met fin au programme. Or le problème avec terminate n'est pas que cette fonction met fin au programme (ce qui est plutôt logique en cas d'erreur) mais plutôt la manière. En effet, le comportement par défaut de terminate est d'appeler abort. Et une terminaison par abort est particulièrement sale car aucune ressource n'est libérée et toutes les fonctions que vous auriez pu mettre en place avec atexit ne seront pas appelées à l'instar d'assert que nous avons déjà rencontrée.

Toutefois, il est possible de spécifier une fonction à la place du terminate standard à l'aide de la fonction set_terminate. Le prototype d'une telle fonction doit être :

void fonction(void);

et bien sur, le code présent dans cette fonction ne doit en aucun cas pouvoir lever une exception ! Personnellement, j'utilise le code suivant :

void terminaison(void)
{
  cout << "Exception non geree" << endl;
  cout << "Terminaison propre du programme" << endl;
  exit(1);
}

Programme 7.9 Exemple de fonction terminate

Rappelons juste qu'à l'inverse de abort, la fonction exit effectue un nettoyage minimal du programme en appelant, en particulier, les fonctions spécifiées avec atexit. Pour être efficace, vous devez placer l'appel à set_terminate en première ligne de main. Par exemple :

int main(int, char **)
{
  set_terminate(terminaison);


  // tout plein de code vachement utile !


  return 0;
}

Programme 7.10 Mise en place de fonction terminate

Mais, avant d'en arriver à main, votre exception aura peut être parcouru du chemin ! Supposons que vous placiez un gestionnaire dans une fonction (ou une méthode) appelée dans la fonction main, comme, par exemple, dans le cas suivant :

void terminaison(void)
{
  cout << "Exception non geree" << endl;
  cout << "Terminaison propre du programme" << endl;
  exit(1);
}


void caVaPlanter(void)
{
  try
  {
    Chaine uneChaine("toto");


    // va generer une erreur de type Chaine::ErreurAccesSuperieur
    cout << laChaine[30] << endl;
  }
  catch (Chaine::ErreurAllocation &e)
  {
    cerr << e.what() << endl;
  }
}




int main(int, char **)
{
  set_terminate(terminaison);
  try
  {
    caVaPlanter();
  }
  catch (Chaine::Erreur &e)
  {
    cerr << "Remontee d'exception "" << e.what() << endl;
    throw;
  }
  return 0;
}

Programme 7.11 Remontée d'une exception

L'exception qui est générée par lachaine[30] est de type Chaine::ErreurAccesSuperieur. Hors, seule Chaine::ErreurAllocation est traitée au niveau de la fonction caVaPlanter. Donc, lorsque l'exception est levée, le contrôle revient à l'appelant, c'est à dire main. Chance l'appel à caVaPlanter était englobé dans un bloc try et disposait d'un gestionnaire idoine ; l'exception va donc être gérée ici.

Et la petite instruction throw, à quoi sert-elle ? et bien, tout simplement à relancer l'exception courante vers le niveau supérieur. Ce qui permet, en particulier d'établir des traitements emboités. En l'occurence, dans notre exemple, le throw va résulter en un appel à terminate.

7.2.6 Les spécificateurs d'exceptions

Vous aurez sans doute noté que les méthodes de la classe std::exception ainsi que leurs redéfinitions présentent une clause throw () dans leur déclaration. Celle-ci signifie que la méthode n'est sensée renvoyer aucune exception.

La syntaxe générale est la suivante :

typeRetour identificateur(params) throw (liste exceptions attendues)

où la liste des exceptions attendues est constituée de types d'exceptions séparées par des virgules.

Par exemple, supposons qu'une méthode de la classe Chaine soit susceptible de renvoyer ErreurAllocation et ErreurAccesInferieur, son prototype pourrait-être :

void methode(void) throw (ErreurAllocation, ErreurAccesInferieur);

Plusieurs points sont importants à noter :

Vous allez me dire : « jusqu'ici, nous avons lancé et traité des exceptions sans utiliser les spécifications, alors que rajoutent-elles ? »

C'est en fait un peu compliqué. Si une méthode dans son code propre où dans le code qu'elle appelle génère une exception qui n'est pas prévue dans sa spécification, alors, un appel à unexpected est effectué sur le champ. Vous voyez tout de suite la difficulté : pour bien utiliser les spécifications, il faut savoir quelles exceptions sont susceptibles d'être levées dans le code appelé ... ce qui nécessite presque obligatoirement que les méthodes / fonctions que vous appelez soient elles-mêmes dotées de spécificateurs d'exception à jour !

Par défaut, unexpected appelle terminate. Si vous avez redirigé terminate par un appel à set_terminate, bien entendu, c'est votre fonction qui sera appelée. Toutefois, vous avez la possibilité de changer ce comportement par défaut en utilisant set_unexpected avec une fonction de prototype void fonction(void) en paramètre. Personnellement, j'utilise :

void nonPrevue(void)
{
  cout << "Exception non prevue" << endl;
  terminate();  
}

Programme 7.12 Exemple de fonction unexpected

et ma fonction main devient alors :

int main(int, char **)
{
  set_terminate(terminaison);
  set_unexpected(nonPrevue);


  // Code mega utile !


  return 0;
}

Programme 7.13 Installation d'une fonction unexpected

7.2.5 Plus d'informations dans les exceptions

Quitte à utiliser des objets, ne pourrions nous pas leur donner plus d'intelligence ? parbleu, nous pourrions, par exemple passer la taille de mémoire incriminée ou le mauvais index !

Réécrivons donc ces classes !

class ErreurAllocation : public Erreur
  {
    public:
      ErreurAllocation(int taille=-1) : Erreur(), tailleProbleme_(taille)
      {}
      
      virtual const char * what(void) const throw ()
      {
        return "Erreur d'allocation sur une chaine";
      }
      int tailleProbleme() const throw ()
      {
        return tailleProbleme_;
      }
    private:
      int tailleProbleme_;
  };
  
  class ErreurAcces : public Erreur
  {
    public:
      ErreurAcces(int mauvaisIndex=0) : Erreur(), indexIncrimine_(mauvaisIndex) {}
      virtual const char *what(void) const throw()
      {
        return "Erreur d'acces sur une chaine avec l'operateur []";
      }
      int indexIncrimine() const throw ()
      {
        return indexIncrimine_;
      }
    private:
      int indexIncrimine_;
  };
  
  class ErreurAccesInferieur : public ErreurAcces
  {
    public:
      ErreurAccesInferieur(int mauvaisIndex=0) : ErreurAcces(mauvaisIndex) {}
      virtual const char *what(void) const throw()
      {
        return "Erreur d'acces sur une chaine avec l'operateur [] et avec indice negatif";
      }      
  };
  
  class ErreurAccesSuperieur : public ErreurAcces
  {
    public:
      ErreurAccesSuperieur(int mauvaisIndex=0) : ErreurAcces(mauvaisIndex) {}
      virtual const char *what(void) const throw()
      {
        return "Erreur d'acces sur une chaine avec l'operateur [] et avec indice au dela de la capacite";
      }      
  };

Programme 7.14 Des exceptions qui contiennent de l'information

7.2.7 Notre exemple revu et corrigé

Intégrons les mécanismes que nous avons vus dans notre exemple :

#ifndef __CHAINE_HXX__
#define __CHAINE_HXX__




#include <string.h>


#include <iostream>
#include <exception>
using namespace std;




class Chaine
{
 
  public:
  
  class Erreur : public exception
  {

    public:
      virtual const char * what(void) const throw ()
      {
        return "Erreur generique sur les chaines";
      }
  };
  
  class ErreurAllocation : public Erreur
  {
    public:
      ErreurAllocation(int taille=-1) : Erreur(), tailleProbleme_(taille)
      {}
      
      virtual const char * what(void) const throw ()
      {
        return "Erreur d'allocation sur une chaine";
      }
      int tailleProbleme() const throw ()
      {
        return tailleProbleme_;

      }
    private:
      int tailleProbleme_;
  };
  
  class ErreurAcces : public Erreur
  {
    public:
      ErreurAcces(int mauvaisIndex=0) : Erreur(), indexIncrimine_(mauvaisIndex) {}
      virtual const char *what(void) const throw()
      {
        return "Erreur d'acces sur une chaine avec l'operateur []";
      }
      int indexIncrimine() const throw ()
      {
        return indexIncrimine_;
      }
    private:
      int indexIncrimine_;
  };
  
  class ErreurAccesInferieur : public ErreurAcces
  {
    public:
      ErreurAccesInferieur(int mauvaisIndex=0) : ErreurAcces(mauvaisIndex) {}
      virtual const char *what(void) const throw()
      {
        return "Erreur d'acces sur une chaine avec l'operateur [] et avec indice negatif";
      }      
  };
  
  class ErreurAccesSuperieur : public ErreurAcces
  {
    public:
      ErreurAccesSuperieur(int mauvaisIndex=0) : ErreurAcces(mauvaisIndex) {}
      virtual const char *what(void) const throw()
      {
        return "Erreur d'acces sur une chaine avec l'operateur [] et avec indice au dela de la capacite";
      }      
  };
  
  
  
   
  // Constructeur par defaut
  Chaine(int taille=16) throw (ErreurAllocation) :
    longueur_(0),
    capacite_(taille)
  {
    if (taille)
    {
      tableau=new char [taille];
      if (!tableau)
        throw ErreurAllocation(taille);
    }
    else
      tableau=0;
  }
  
  // Constructeur prenant un pointeur sur char 
  Chaine(const char *pStr) throw (ErreurAllocation) 
  {
    if (pStr)
    {
      int taille=strlen(pStr);
      longueur_=capacite_=taille;
      if (taille)
      {
        tableau = new char [taille+1];
        if (tableau == 0)
            throw ErreurAllocation(taille+1);
        strcpy(tableau,pStr);
      }
      else
        tableau=0;      
    }
    else
    {
      longueur_=0;
      capacite_=0;
      tableau=0;
    }
  }
  
  // Constructeur de recopie
  Chaine(const Chaine &uneChaine) throw (ErreurAllocation)
  {
    clonage(uneChaine);
  }
  
  ~Chaine()
  {
    nettoyage();
  }
  
  // operateur d'affectation
  
  Chaine &operator=(const Chaine &uneChaine) throw (ErreurAllocation)
  {
    if (this != &uneChaine)
    {
      nettoyage();
      clonage(uneChaine);
    }
    return *this;
  }
  
  const char operator[](int index) const  throw (ErreurAcces)
  {
    if (index < 0) 
      throw ErreurAccesInferieur(index);
    if (index >= longueur_)
      throw ErreurAccesSuperieur(index); 
    return tableau[index];
  }
  
  char &operator[](int index) throw (ErreurAcces)
  {


    if (index < 0) 
      throw ErreurAccesInferieur(index);
    if (index >= longueur_)
      throw ErreurAccesSuperieur(index);
    return tableau[index];
  }
  
  private:
    int   longueur_;
    int   capacite_;
    char *tableau;
    
    
    void clonage(const Chaine &uneChaine) throw (ErreurAllocation)
    {
      longueur_=uneChaine.longueur_;
      capacite_=uneChaine.capacite_;
      tableau = new char [capacite_+1];
      if (tableau == 0)
        throw ErreurAllocation();
      if (longueur_)
        strcpy(tableau,uneChaine.tableau);
    }
    
    void nettoyage(void)
    {
      delete [] tableau;
    }
    
    
};




#endif

Programme 7.15 Version finale de la classe et de ses exceptions

et un petit programme d'essai :

#include "chaine.hxx"


#include <iostream>
#include <exception>


using namespace std;




void terminaison(void)
{
  cout << "Exception non geree" << endl;
  cout << "Terminaison propre du programme" << endl;
  exit(1);

}


void nonPrevue(void)
{
  cout << "Exception non prevue" << endl;
  terminate();  
}






int main(int, char **)
{
  set_terminate(terminaison);
  set_unexpected(nonPrevue);  
  
  try
  {
    Chaine laChaine("toto");
    cout << laChaine[3] << endl;
  }
  catch (Chaine::ErreurAllocation &e)
  {
    cerr << e.what() << " taille a probleme " << e.tailleProbleme() << endl;
  }
  catch (Chaine::ErreurAcces &e)
  {
    cerr << e.what() << " sur l'index " << e.indexIncrimine() << endl;
  }
  catch (Chaine::Erreur &e)
  {
    cerr << "Autre erreur sur chaine : " << e.what() << endl;
  }
  catch (exception &e)
  {
    cout << "Autre exception standard : " << e.what() << endl;
  }
  catch (...)
  {
    cout << "Exception non standard" << endl;
  }


  return 0;
}

Programme 7.16 Exemple d'utilisation de la version finale

Amusez-vous à lancer de fauses exceptions dans les méthodes pour voir comment réagissent unexpected et terminate !