next up previous contents
Next: Exceptions en C++ Up: Exceptions Previous: Exceptions

Généralités

Le traitement des erreurs est un domaine complexe, au cours des exemples précédents, on a souvent rencontré des fonctions qui retournent en principe une valeur, mais qui devraient avoir un comportement particulier en cas d'``erreur'' :

template <class T>
Table{
   T *tab;
   int sz;
public:
   Table(int n){
      tab=new T[sz=n];
      // que faire si tab==0 ?
   }
   T& operator[](int i){
      if(i<sz && i>=0)
          return tab[i];
      // sinon ??
    }
    //...
};
Une solution possible serait dans ces deux cas de considérer qu'il s'agit d'une erreur fatale, et donc, de tout arrêter, c'est ce que fait la fonction assert :
      //...
   Table(int n){
      tab=new T[sz=n]; 
      assert(tab != 0);
   }
      //...
      
   T& operator[](int i){
      assert(i<sz && i>=0);
      return tab[i];
   }
Dans le cadre d'une grande application, censée souvent exister pour toujours, cette solution n'est pas applicable.

On pourrait aussi avoir une valeur spéciale, servant toujours à notifier une erreur. C'est un peu ce qui se passe dans le système unix, où une valeur retournée égale à -1 correspond à un comportement en erreur pour un appel système.

Une autre caractéristique du traitement des erreurs est qu'il oblige, en général, si on ne veut pas stopper définitivement le programme, à se brancher sur un point de reprise qui n'est pas nécessairement directement lié au point où l'erreur a pu apparaître. En C, on peut parfois utiliser, pour traiter des erreurs, les fonctions setjmp() et longjmp(), qui permettent de ``sauvegarder'' le contexte de la pile et de se brancher directement sur un contexte sauvegardé. En C++, le problème est plus complexe, un branchement inconditionnel de ce genre n'est pas directement possible, puisque, en principe, il faut qu'à chaque sortie de fonction les destructeurs d'objets locaux soient appelés et donc le ``dépilage'' est obligatoire et plus complexe qu'un simple branchement.

En C++, les exceptions permettront le traitement de ces conditions anormales. On définit à certains points du programme des traite-exceptions, et si une condition anormale apparaît, une exception sera levée, et le contrôle du programme sera transféré jusqu'au premier (c'est-à-dire le plus récent) traite-exception rencontré. Même s'il est surtout conçu pour des traitements d'erreurs, les exceptions peuvent aussi servir dans des conditions normales et permettre par exemple un dépilage rapide.

Le C++, va permettre d'associer à une exception une expression (et donc un type) qui permettra de sélectionner le traite-exception associé.

      //...
Table(int n){
   tab=new T[sz=n]; 
   if(tab==0) throw Alloc();
}
      //...
      
T& operator[](int i){
   if(i<sz && i>=0)
       return tab[i];
   else throw Interval();
}
Dans ce cas, Alloc et Interval sont définis comme des classes et Alloc(), Interval() les appels au constructeur par défaut de ces classes.

class Alloc{};
class Interval{};
void f(){
    //...
    try_bloc{    // protegé
       Table<int> t[100];
       //...
       t[i]=12;
       //...
    }
    catch(Alloc){
        // ...
    }
}
void g(){
    //...
    try_bloc{
         f();   
         //...
    }
    catch( ... ){  // capte toutes les exceptions
       // ... 
    }
}
void main(){ g();}
Après l'appel de g(), une exception Alloc sera captée et traitée par f(), toutes les autres seront traitées par g();

En utilisant les classes, en particulier les constructeurs et l'héritage, on peut structurer les exceptions en utilisant l'héritage et la liaison dynamique.

Par exemple, on pourrait avoir :

class Excep_Gen{ 
public:
   virtual void avertir(){
       cerr<<"exception generale\n";
   }
//...
};
class Definitif: Excep_Gen{
//...
};
class Alloc:public Excep_Gen{
   size_t demande;    
public:
   Alloc(int n){ demande=n; }
   void avertir(){
        Excep_Gen::avertir();
        cerr<< "allocation "<<demande<<" refusee ";
   }
};
           
      
template <class T>
class Table{
    // ... comme avant
public:
    struct Interval{  //classe locale pour exceptions de Table
        int indice;   // valeur qui provoque le depassement
        int ind_max;  // valeur maximale possible
        Interval(int i){ 
           indice=i; ind_max=sz;
        }
     //...
   Table(int n){
      tab=new T[sz=n]; 
      if(tab==0) throw Alloc();
   }
      //...
      
   T& operator[](int i){
      if(i<sz && i>=0)
          return tab[i];
      else throw Interval();
   }
};
Et un client de la classe pourrait alors définir des traite-exceptions :
void f(){
   try{
      Table<int> t[1000];   // peut provoquer Alloc
      //...
      t[i]=t[j] ;           // peut provoquer Interval
      //...
    }
    catch(Alloc & a){
        a.avertir();
        // traitement pour une erreur d'allocation 
    }
    catch(Interval){
         cerr<<"erreur interval";
    }
}


next up previous contents
Next: Exceptions en C++ Up: Exceptions Previous: Exceptions


Mon Oct 20 14:02:48 MET 1997