FAQ C++Consultez toutes les FAQ

Nombre d'auteurs : 35, nombre de questions : 368, dernière mise à jour : 17 novembre 2018  Ajouter une question

 

Cette FAQ a été réalisée à partir des questions fréquemment posées sur les forums de http://www.developpez.com et de l'expérience personnelle des auteurs.

Je tiens à souligner que cette FAQ ne garantit en aucun cas que les informations qu'elle propose sont correctes ; les auteurs font le maximum, mais l'erreur est humaine. Cette FAQ ne prétend pas non plus être complète. Si vous trouvez une erreur ou si vous souhaitez devenir rédacteur, lisez ceci.

Sur ce, nous vous souhaitons une bonne lecture.


SommaireUtilisation des exceptions (13)
précédent sommaire suivant
 

Les exceptions sont un nouveau moyen de gérer les erreurs dans les programmes. La grande différence vis à vis du classique code d'erreur renvoyé par une fonction est qu'une exception se propage depuis l'appelé vers l'appelant jusqu'à ce qu'elle rencontre un bloc de code qui s'occupe de la traiter. Au contraire d'un code d'erreur qui peut être ignoré (ce qui est malheureusement souvent le cas) une exception doit être traitée. Si elle ne l'est pas dans la fonction qui en est à l'origine, elle doit l'être dans l'une des fonctions appelantes. Le compilateur s'occupe tout seul de faire en sorte que l'exception remonte le long de la pile des appels jusqu'à l'endroit où un bloc a été prévu pour la traiter. Cela permet donc de facilement faire « remonter » les erreurs des fonctions appelées vers les fonctions appelantes. L'apparition d'une exception interrompt l'exécution normale du programme et provoque sa reprise dans le gestionnaire d'exception le plus proche (qui peut se trouver beaucoup plus en amont dans une fonction appelante).
Le programmeur n'a donc plus à se soucier de tester la réussite ou non des fonctions qu'il appelle au moyen d'un grand nombre de tests comme dans l'exemple suivant :

Code c++ : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// ces fonctions renvoient false en cas d'erreur 
bool F1(); 
bool F2(); 
bool F3(); 
bool F4(); 
  
bool Test1() 
{ 
    // appeler F1 et F2 
    if ( !F1() ) 
    { 
        return false; 
    } 
    if ( !F2() ) 
    { 
        return false; 
    } 
    return true; 
} 
  
bool Test2() 
{ 
    // appeler Test1 et F3 
    if ( !Test1() ) 
    { 
        return false; 
    } 
    if ( !F3() ) 
    { 
        return false; 
    } 
    return true; 
} 
  
bool Test3() 
{ 
    // appeler Test2 et F4 
    if ( !Test2() ) 
    { 
        return false; 
    } 
    if ( !F4() ) 
    { 
        return false; 
    } 
    return true; 
} 
  
int main() 
{ 
    if ( !Test3() ) 
    { 
        std::cerr << "Une erreur est survenue, mais je ne sais pas où !"; 
    } 
}
Les exceptions permettent de grandement simplifier le code précédent tout en améliorant la qualité du renseignement sur l'origine de l'erreur :

Code c++ : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// ces fonctions lèvent des exceptions en cas d'erreur 
void F1(); 
void F2(); 
void F3(); 
void F4(); 
  
void Test1() 
{ 
    F1(); 
    F2(); 
} 
  
void Test2() 
{ 
    Test1(); 
    F3(); 
} 
  
void Test3() 
{ 
    Test2(); 
    F4(); 
} 
  
int main() 
{ 
    try 
    { 
        Test3(); 
    } 
    catch ( const std::bad_alloc & ) 
    { 
         std::cerr << "Erreur : mémoire insuffisante.\n"; 
    } 
    catch ( const std::out_of_range & ) 
    { 
         std::cerr << "Erreur : débordement de mémoire.\n"; 
    } 
}

Mis à jour le 22 novembre 2004 Aurelien.Regat-Barrel

Les exceptions sont déclenchées grâce à l'utilisation du mot-clé throw :

Code c++ : Sélectionner tout
1
2
// lève une exception de type e 
throw e;
Les exceptions peuvent être de n'importe quel type (type de base du langage ou classe quelconque). Mais il est conseillé d'utiliser une classe dérivant de la classe standard std::exception définie dans le fichier d'en-tête standard <exception>. Cette classe dispose d'une fonction membre what() qui renvoie une description de l'exception.

Code c++ : Sélectionner tout
1
2
3
4
5
6
7
8
9
try 
{ 
    // instructions pouvant déclencher des exceptions 
    // dérivant de std::exception 
} 
catch ( const std::exception & e ) 
{ 
    std::cerr << e.what(); 
}
Les exceptions doivent être de préférence déclenchées par valeur, et attrapée par référence. Déclencher une exception par pointeur pose en effet un problème :

Code c++ : Sélectionner tout
1
2
3
4
5
6
7
8
9
try 
{ 
    // déclencher une exception par pointeur 
    throw new int( 10 ); 
} 
catch ( const int * e ) 
{ 
    std::cerr << "Erreur numéro " << *e; 
}
Le code précédent fonctionne mais une question subsiste : comment est libéré le pointeur alloué dans le bloc try ? La réponse est qu'il ne l'est pas, et il se produit donc une fuite de mémoire. Le problème de déclencher une exception par pointeur est donc de savoir à qui incombe la responsabilité de libérer ce dernier. Voilà pourquoi on préfère lever des exceptions par valeur, et les attraper par référence (de préférence constante) afin de permettre le polymorphisme et d'améliorer les performances en évitant une recopie de l'objet.

Code c++ : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream> 
#include <stdexcept> 
  
int main() 
{ 
    try 
    { 
        // std::logic_error est une classe standard 
        // qui dérive de std::exception 
        throw std::logic_error( "Exemple d'exception" ); 
    } 
    catch ( const std::exception & e ) 
    { 
        // affiche "Exemple d'exception" 
        std::cerr << e.what(); 
    } 
}
Un cas particulier est celui des chaînes de caractères littérales :

Code c++ : Sélectionner tout
1
2
3
4
5
6
7
8
try 
{ 
    throw "Message d'erreur"; 
} 
catch ( const char * Msg ) 
{ 
    std::cerr << Msg; 
}
Dans l'exemple précédent, bien qu'on utilise un pointeur, il n'y a pas de fuite de mémoire tout simplement parce qu'il n'y a pas d'allocation dynamique. Ce serait même une grosse erreur de libérer ce pointeur avec delete [] car ce dernier pointe vers une zone de mémoire spéciale (généralement en lecture seule) qui contient l'ensemble de chaînes de caractères littérales utilisées dans le programme. Donc, à priori, utiliser des chaînes littérales est une bonne idée, mais cela est néanmoins déconseillé car leur utilité est vite limitée du fait de l'impossibilité de formater les chaînes pour donner la valeur d'une variable par exemple. Il y a fort à craindre que tôt ou tard un programmeur voudra effectuer cette opération et provoquera alors inconsciemment une fuite de mémoire (car dans le bloc catch il n'y a aucun moyen de distinguer une chaîne littérale d'une chaîne allouée avec new []).
Si vous persistez à vouloir utiliser de simple chaînes de caractères au lieu d'une classe dérivant de std::exception, utilisez au moins le type chaîne de caractères du C++ : std::string.

Code c++ : Sélectionner tout
1
2
3
4
5
6
7
8
try 
{ 
    throw std::string( "Message d'erreur" ); 
} 
catch ( const std::string & Msg ) 
{ 
    std::cerr << Msg; 
}

Mis à jour le 22 novembre 2004 Aurelien.Regat-Barrel

Le code susceptible de déclencher des exceptions doit être placé dans un bloc try...catch (essaye...attrape) de cette manière :

Code c++ : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
int * ptr; 
try 
{ 
    // tenter d'allouer 100 entiers 
    ptr = new int [ 100 ]; 
} 
catch ( const std::bad_alloc & ) 
{ 
    // échec de l'allocation 
}
On peut mettre autant de blocs catch qu'il y a d'exceptions à rattraper.

Code c++ : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
try 
{ 
    // créer un tableau de taille 10 
    std::vector<int> tableau( 10 ); 
    // accéder au 11° élément 
    tableau.at( 10 ); 
} 
catch ( const std::exception & Exp ) 
{ 
    std::cerr << "Erreur : " << Exp.what() << ".\n"; 
} 
catch ( const std::bad_alloc & ) 
{ 
    std::cerr << "Erreur : mémoire insuffisante.\n"; 
} 
catch ( const std::out_of_range & ) 
{ 
    std::cerr << "Erreur : débordement de mémoire.\n"; 
}
Les exceptions levées dans le bloc try vont être filtrées par les différents blocs catch suivant leur ordre d'apparition. Ce filtrage est effectué en fonction du type de l'exception, et est naturellement sensible au polymorphisme. C'est-à-dire que le premier bloc catch rencontré capable de traiter l'exception levée est celui qui est utilisé. Les autres sont ignorés, même si certains seraient mieux adaptés. En l'occurrence, dans l'exemple précédent, l'exception out_of_range est levée et ce serait tout naturellement le dernier bloc catch qui devrait la traiter. Mais std::out_of_range dérive de std::exception qui est la classe de base pour les exceptions standards, et donc c'est le premier bloc catch qui est appelé.
Il existe aussi un moyen d'attraper toutes les exceptions, en utilisant une ellipse (...) comme type de l'exception. Mais alors il n'y a aucun moyen de connaître l'origine et le type de l'exception (sauf à la relancer et la traiter dans un nouveau bloc try...catch). L'utilisation de cette forme générique doit être restreinte car elle ne permet de savoir si l'exception capturée peut être traitée et ignorée ou si elle nécessite de terminer le programme (corruption de la mémoire, etc.). On l'utilise en général comme dernier recours.

Code c++ : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
try 
{ 
    // créer un tableau de taille 10 
    std::vector<int> tableau( 10 ); 
    // accéder au 11° élément 
    tableau.at( 10 ); 
} 
catch ( const std::bad_alloc & ) 
{ 
    std::cerr << "Erreur : mémoire insuffisante.\n"; 
} 
catch ( const std::out_of_range & ) 
{ 
    std::cerr << "Erreur : débordement de mémoire.\n"; 
} 
catch ( const std::exception & Exp ) 
{ 
    std::cerr << "Erreur : " << Exp.xhat() << ".\n"; 
} 
catch ( ... ) // traite toutes les autres exceptions 
{ 
    std::cerr << "Erreur inconnue.\n"; 
}
Il est donc important de faire apparaître les blocs catch des classes dérivées en premier.
Notez que les exceptions sont récupérées par référence, comme cela est expliqué dans la question Pourquoi faut-il capturer les exceptions par référence ?. Ces références ne sont cependant pas des références sur l'objet initial qui est à l'origine de l'exception, mais sur une copie de celui-ci, car l'objet initial risque d'être détruit si l'on quitte la fonction qui a levé l'exception.

Mis à jour le 17 mars 2008 Aurelien.Regat-Barrel

Comme discuté dans la question Comment lever une exception ?, il est fortement recommandé de lever des exceptions par valeur. En revanche, il vaut mieux les capturer par référence et non pas par valeur. Tout d'abord cela permet d'éviter une recopie, mais aussi et surtout cela permet de conserver le polymorphisme. L'exemple suivant illustre les problèmes posés par un traitement des exceptions par valeur :

Code c++ : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream> 
#include <stdexcept> 
  
int main() 
{ 
    using namespace std; 
  
    try 
    { 
        // std::logic_error hérite de std::exception 
        throw logic_error( "exception de test" ); 
    } 
    catch ( exception e ) // traitement par valeur 
    { 
        cerr << e.what(); 
    } 
}
Cet exemple affiche le message Unknown exception. Si l'on remplace le traitement par valeur par un traitement par référence :

Code c++ : Sélectionner tout
catch ( const exception & e ) // traitement par référence
Alors on obtient le message attendu exception de test. Ceci est dû au fait que le polymorphisme nécessite d'utiliser un pointeur ou une référence, autrement dans notre cas l'objet de type std::logic_error est « tronqué » en un objet de type std::exception. La fonction membre what appelée n'est donc pas celle de std::logic_error mais celle de std::exception, qui n'est pas d'une grande utilité.
Donc à moins de rechercher volontairement ce comportement, il est recommandé de traiter les exceptions par référence, de préférence constantes afin de permettre au compilateur d'effectuer des optimisations.

Mis à jour le 22 novembre 2004 Aurelien.Regat-Barrel

Malheureusement, non, ce mécanisme n'est pas possible. Un catch ne pouvant capturer qu'un seul type d'exceptions, il faut définir autant de blocs try/catch qu'il y a d'exceptions possibles.

L'utilisation de

Code c++ : Sélectionner tout
1
2
3
4
5
try { 
    // ... 
} catch(...) { 
    // ... 
}
permet de capturer toutes les exceptions pouvant survenir, mais il est, dans ce cas, impossible de faire la distinction.

Mis à jour le 9 octobre 2003 LFE

Le mot-clé throw permet de lever une nouvelle exception, mais aussi de relancer celle qui est en cours de traitement.

Code c++ : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <iostream> 
#include <stdexcept> 
  
void Test() 
{ 
    try 
    { 
        throw std::logic_error( "Exception de test" ); 
    } 
    catch ( const std::logic_error & e ) 
    { 
        std::cerr << "L'exception '" << e.what() 
                  << "' a été levée et va être relancée.\n"; 
        throw; // relancer l'exception courante 
    } 
} 
  
int main() 
{ 
    try 
    { 
        Test(); 
    } 
    catch ( const std::logic_error & e ) 
    { 
        std::cerr << "Erreur : " << e.what() << ".\n"; 
    } 
}

Mis à jour le 22 novembre 2004 Aurelien.Regat-Barrel

Lorsqu'une exception est déclenchée, le compilateur recherche un bloc catch capable de la traiter. S'il n'en trouve pas, il remonte la pile d'exécution (déroulage de la pile) afin d'en trouver un plus en amont dans la hiérarchie des appels. Dépiler un appel revient à quitter une fonction. A cette occasion ses objets locaux sont détruits et les destructeurs appelés, ce qui permet de quitter proprement la fonction en libérant toutes les ressources acquises si les destructeurs on été bien écrits. L'objet qui a servi à lever l'exception est lui même détruit car il est local à la fonction. C'est pourquoi l'objet qui est transmis aux blocs catch est toujours une copie de l'objet initial qui a déclenché l'exception.
Si la pile des appels est vidée (donc que l'on est arrivé à main) et qu'aucun bloc catch satisfaisant n'a été trouvé, la fonction standard terminate est appelée ce qui provoque par défaut un arrêt pur et simple du programme. Ce comportement peut être modifié au moyen de la fonction set_terminate définie dans l'en-tête standard <exception>. Cette fonction installe un nouveau gestionnaire et renvoie l'adresse du précédent. Elle s'utilise de cette manière :

Code c++ : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream> 
#include <exception> 
  
// ancien gestionnaire 
void (*old_handler)(); 
  
// gestionnaire personnalisé 
void my_handler() 
{ 
    std::cerr << "Exception inattendue.\n"; 
    // appel du gestionnaire par défaut 
    (old_handler)(); 
} 
  
int main() 
{ 
    // installer notre gestionnaire personnalisé 
    old_handler = set_terminate( my_handler ); 
    // lever une exception que l'on ne traite pas 
    throw "test"; 
}
Le code précédent provoque le résultat suivant avec le compilateur Visual C++ 7.1 :

Exception inattendue.
This application has requested the Runtime to terminate it in an unusual way.
Please contact the application's support team for more information.

Mis à jour le 22 novembre 2004 Aurelien.Regat-Barrel

N'importe quel type de base ou classe C++ peut être utilisé comme type d'exception. Mais il est préférable de créer son type qui hérite de la classe de base standard pour les exceptions : std::exception définie dans l'en-tête <exception>. Cette classe possède une fonction membre virtuelle what() qu'il convient de redéfinir :

Code c++ : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <iostream> 
#include <sstream> 
#include <exception> 
  
class my_exception : public std::exception 
{ 
public: 
    my_exception( const char * Msg, int Line ) 
    { 
        std::ostringstream oss; 
        oss << "Erreur ligne " << Line << " : " 
            << Msg; 
        this->msg = oss.str(); 
    } 
  
    virtual ~my_exception() throw() 
    { 
  
    } 
  
    virtual const char * what() const throw() 
    { 
        return this->msg.c_str(); 
    } 
  
private: 
    std::string msg; 
}; 
  
int main() 
{ 
    try 
    { 
        throw my_exception( "exception test", __LINE__ ); 
    } 
    catch ( const std::exception & e ) 
    { 
        std::cerr << e.what() << "\n"; 
    } 
}
Cet exemple produit le résultat suivant :

Erreur ligne 29 : exception test

Mis à jour le 17 mars 2008 Aurelien.Regat-Barrel

Tout à fait. C'est même une des seules manières d'indiquer que l'initialisation de l'objet a échoué. Il faut cependant être prudent car une exception levée dans un constructeur peut être à l'origine de fuites de mémoires ou d'autres problèmes de non libération de ressources. C'est le cas dans l'exemple suivant :

Code c++ : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Test 
{ 
public: 
    // exception bad_alloc en cas de mémoire insuffisante 
    Test( int A, int B ) : 
        tableau1( 0 ), 
        tableau2( 0 ) 
    { 
        // ici tableau1 et tableau2 valent NULL, donc en cas d'échec d'allocation 
        // on peut appeler delete [] sans problème dans le destructeur 
        this->tableau1 = new int[ A ]; 
        this->tableau2 = new int[ B ]; 
    } 
    ~Test() 
    { 
        delete [] this->tableau2; 
        delete [] this->tableau1; 
    } 
  
private: 
    int * tableau1; 
    int * tableau2; 
};
L'idée du code ci-dessus est d'initialiser les pointeurs tableau1 et tableau2 à zéro ainsi si l'une des allocations échoue on peut tout de même appeler en toute sérénité delete [] dans le destructeur et ainsi éviter les fuites de mémoire (souvenez vous, appeler delete sur un pointeur nul ne fait rien, voir Que se passe-t-il si je fais un delete sur un pointeur qui vaut NULL ?).
Le problème est que si une exception est levée lors de la construction d'un objet, c'est donc que celle-ci a échoué, et donc que l'objet n'est pas créé. Comme il n'est pas créé, il n'a pas à être détruit, et donc son destructeur ne sera pas appelé. Autrement dit, si une exception est levée dans le constructeur d'un objet, son destructeur ne sera pas appelé.
Il faut donc toujours s'assurer que le code contenu dans le constructeur est exception safe, c'est-à-dire qu'il résiste aux exceptions en ne provoquant pas de pertes de ressources. Dans notre exemple précédent cela signifie qu'il faut gérer l'exception bad_alloc de cette manière :

Code c++ : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class Test 
{ 
public: 
    // exception bad_alloc en cas de mémoire insuffisante 
    Test( int A, int B ) : 
        tableau1( 0 ) 
    { 
        // ici tableau1 vaut NULL, donc en cas d'échec d'allocation 
        // on peut appeler delete [] sans problème 
        try 
        { 
            this->tableau1 = new int[ A ]; 
            this->tableau2 = new int[ B ]; 
        } 
        catch ( const std::bad_alloc & ) 
        { 
            // tableau2 n'a pas été alloué quoi qu'il arrive 
            // libérer tableau1 s'il a pu être alloué 
            delete [] this->tableau1; 
            // relancer l'exception 
            throw; 
        } 
    } 
    ~Test() 
    { 
        delete [] this->tableau2; 
        delete [] this->tableau1; 
    } 
  
private: 
    int * tableau1; 
    int * tableau2; 
};
Le nouveau code produit toujours une exception bad_alloc en cas d'échec d'allocation, mais cette fois-ci il n'y a plus de fuite de mémoire. Ce qui a pu être alloué est libéré, et l'exception bad_alloc capturée est relancée via l'instruction throw (à ce sujet lire Comment relancer une exception que l'on a capturé ?).
Il est à noter que si en cas d'exception dans le constructeur le destructeur n'est pas appelé, tous les membres construits jusqu'au point de l'exception sont quant à eux bien détruits.

Mis à jour le 22 novembre 2004 Aurelien.Regat-Barrel Luc Hermitte

Il est possible de lever une exception dans un destructeur, mais c'est extrêmement déconseillé et considéré comme une très mauvaise pratique. La raison en est simple : si la destruction d'un objet échoue, que faut-il faire ? Mais aussi un autre problème plus grave peut apparaître. Lorsqu'une exception est levée, la pile des appels est remontée (on parle de stack unwinding ou déroulage de la pile) et à cette occasion les objets locaux de la fonction que l'on s'apprête à quitter sont détruits. Donc leur destructeur respectif est appelé. Si l'un d'entre eux vient à lever une exception, la situation devient alors très complexe : laquelle des deux exceptions faut-il gérer ? N'oubliez pas qu'à ce moment nous ne sommes toujours pas dans un bloc catch, mais en train de nous y rendre en quittant les fonctions appelées qui nous en sépare.
Or, en quittant l'une d'entre elles, on détruit un objet qui lance une nouvelle exception, et nous nous retrouvons alors avec deux exceptions à traiter en même temps. La situation étant insoluble, la norme définit que la fonction standard terminate est appelée dans un tel cas, ce qui provoque la fin brutale du programme.
Pour cette très bonne raison, il est important que les destructeurs ne lèvent jamais d'exceptions. On peut s'en assurer en appelant uniquement des fonctions n'échouant jamais (voir Comment indiquer qu'une fonction ne lève jamais d'exception ?).

Mis à jour le 22 novembre 2004 Aurelien.Regat-Barrel

Il est possible d'utiliser noexcept pour le faire, et il est particulièrement recommandé de le faire pour certaines opérations comme les constructeurs par déplacement [Ajouter un lien vers une nouvelle entrée de la faq à créer sur le sujet].
Cette déclaration peut s'utiliser de manière simple ou conditionnelle :

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 
void f1() noexcept // Cette fonction ne lance pas d'exceptions 
{ 
  //... 
}  
 
void f2() noexcept(true) // Cette fonction non plus 
{ 
  //... 
}  
 
void f3() noexcept(noexcept(g) && noexcept(h)) // Cette fonction ne lance pas d'exceptions si g et h sont aussi déclarés noexcept 
{ 
  //... 
}
Si une fonction ayant promis d'être noexcept laisse quand même échapper une exception, la fonction std::terminate est appelée (ce qui interrompt le remontée de la pile d'appels).
Certaines fonctions sont par défaut noexcept : Les destructeurs et les operator delete.

À noter : Les spécifications d’exceptions sont dépréciées, et leur usage n'est pas recommandé.

Mis à jour le 2 juillet 2016 JolyLoic

Il n'y a pas de moyen direct. Utiliser une spécification d'exception vide permet de simuler ça, et certains compilateurs considèrent ce cas un peu différemment d'une spécification d'exception non vide:

Code : Sélectionner tout
1
2
3
void Test() throw () 
{ 
}
Si une exception s'échappe quand même de la fonction, la fonction std::unexpected est appelée.

Mis à jour le 22 novembre 2004 Aurelien.Regat-Barrel JolyLoic

Dans certains langages (tels que Java ou C#), il est possible de créer un bloc finally à la suite d'un bloc try...catch afin de s'assurer qu'une opération (de libération de ressource par exemple) soit bien effectuée. Par exemple, en C++ on ne peut pas écrire ceci :

Code c++ : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
char * buffer = new char[ 100 ]; 
try 
{ 
    // opération susceptible de lever une exception 
} 
finally 
{ 
    // s'assurer que la mémoire est libérée 
    delete [] buffer; 
}
l'équivalent de l'écriture ci-dessus serait :

Code c++ : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
char * buffer = new char[ 100 ]; 
try 
{ 
    // opération susceptible de lever une exception 
} 
catch ( ... ) 
{ 
    // éviter les fuites de mémoire 
    delete [] buffer; 
    // relancer l'exception 
    throw; 
} 
// tout s'est bien passé, libérer la mémoire 
delete [] buffer;
Mais il ne s'agit là que d'une traduction en C++ d'une approche issue d'un autre langage. Or, quand on programme dans un langage, il convient de le faire selon les concepts propres à ce langage, et non avec ceux d'un autre. L'approche C++ à ce problème consiste à encapsuler cette gestion au sein d'un objet qui s'assurera dans son destructeur de la bonne libération de la ressource qu'il gère. Ainsi, on est assuré de ne pas avoir de fuite même en cas d'exception, tout en ayant une écriture plus légère car le bloc try...catch devient inutile dans ce cas.
Ce principe s'appelle le RAII, et est développé dans la question Comment gérer proprement des allocations / désallocations de ressources ? Le RAII !.

Mis à jour le 17 octobre 2005 Aurelien.Regat-Barrel

Proposer une nouvelle réponse sur la FAQ

Ce n'est pas l'endroit pour poser des questions, allez plutôt sur le forum de la rubrique pour ça


Réponse à la question

Liens sous la question
précédent sommaire suivant
 

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2019 Developpez Developpez LLC. Tous droits réservés Developpez LLC. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez LLC. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

 
Contacter le responsable de la rubrique C++

Partenaire : Hébergement Web