OVH Cloud OVH Cloud

Exception multiples

114 réponses
Avatar
bernard tatin
J'ai un problème d'exceptions multiples qui, je pense, est bien décrit
par le code suivant :

// une exception
class Excp {
public:
Excp(char *msg) : message (msg) {}
virtual ~Excp() {}
inline string& getMessage () {
return message;
}
protected:
string message;
};

// une classe simple
class B {
public:
B(int j) : k(j) {}
inline void run () {
throw Excp("exception imprévue dans le run");
}
virtual ~B () { throw Excp("Bad B"); }
private:
int k;
};

void test_excp () {
try {
try {
B b(-5);
b.run();
std::cout << "Création de B ... et sortie" << std::endl;
}
catch (Excp &e) {
std::cout << "Exception " << e.getMessage() << std::endl;
}
}
catch (Excp &e) {
std::cout << "Exception " << e.getMessage() << std::endl;
}
catch (...) {
std::cout << "Exception inconnue" << std::endl;
}
}

int main () {
test_excp ();
return 0;
}

Je comprend que l'exception déclenchée dans b.run() provoque la sortie
du bloc et donc la destruction de l'objet b. Une nouvelle exception est
alors levée. Je pensais que mes try/catch résolverais le problème, mais
ce n'est pas le cas. J'ai un message
Executable “tests” has exited due to signal 6 (SIGABRT).
Et rien d'autre. C'est un vrai plantage. Si je supprime l'un des deux
throw, j'ai le résultat attendu.

J'utilise gcc 3.3 sur MacOS 10.3.5.

Est-ce que quelqu'un a des explications ? Je sais comment m'en dépétrer,
ce qui m'intéresse c'est le pourquoi du comment.

Merci.

Bernard.

10 réponses

1 2 3 4 5
Avatar
Andre Heinen
On Wed, 27 Oct 2004 11:45:44 +0200, Fabien LE LEZ
wrote:

Disons qu'un destructeur peut effectivement lancer une exception en
cas de crise grave (i.e. le programme ne peut vraiment pas continuer),


Je ne ferais pas ça, même en cas de crise grave, au cas où un
jour je réutiliserais la classe en question dans un autre
programme. On ne doit jamais laisser une exception s'échapper
d'un destructeur.

--
Andre Heinen
My address, rot13-encoded: n qbg urvara ng rhebcrnayvax qbg pbz

Avatar
Fabien LE LEZ
On Wed, 27 Oct 2004 13:15:25 +0200, Andre Heinen
:

Je ne ferais pas ça,


Moi non plus.


--
;-)

Avatar
Andre Heinen
On Tue, 26 Oct 2004 23:52:00 +0200, Fabien LE LEZ
wrote:

En gros, garde à l'esprit qu'on ne lance jamais d'exception dans un
destructeur, et tout ira bien.


Plus exactement, un destructeur ne doit jamais déclencher
d'exception. C'est-à-dire qu'on peut sans problème déclencher
une exception dans un destructeur, mais à condition de la
capturer et de ne pas la laisser s'en "échapper".

--
Andre Heinen
My address, rot13-encoded: n qbg urvara ng rhebcrnayvax qbg pbz

Avatar
Andre Heinen
On Wed, 27 Oct 2004 12:08:18 +0200, bernard tatin
wrote:

J'espérais que cette exception soit récupérable et que toutes les
resources allouées soient désallouées proprement. Abort et exit sont un
peu plus brutales.

Finalement, j'ai remplacé l'exception par un exit() avec un code qui me
va bien.


Les trois grandes façons de terminer un programme sont return
dans main(), exit() et abort().

return dans main() provoquera la destruction correcte de toutes
les variables, y compris les variables locales à main().

exit() détruira correctement les variables globales (et statiques
et d'expace de nom) mais pas les variables locales à main(),
puisqu'on ne "quitte" pas main().

abort() ne détruit rien du tout et arrête le programme tout de
suite.

Le choix n'est pas nécessairement facile. Si on appelle exit(),
on court le risque d'appeler des destructeurs qui peuvent
eux-mêmes tenter d'arrêter le programme en appelant exit(), d'où
une récursion infinie. Quant à abort(), elle ne présente pas ce
problème, mais ne détruit pas correctement tous les objets.
Dilemme...

--
Andre Heinen
My address, rot13-encoded: n qbg urvara ng rhebcrnayvax qbg pbz

Avatar
Fabien LE LEZ
On Wed, 27 Oct 2004 13:24:06 +0200, Andre Heinen
:

Le choix n'est pas nécessairement facile. Si on appelle exit(),
on court le risque d'appeler des destructeurs qui peuvent
eux-mêmes tenter d'arrêter le programme en appelant exit()


Et surtout, il y a des chances pour qu'on soit arrivé à une extrêmité
telle que le programme ne peut vraiment plus tourner -- même pas pour
nettoyer la mémoire.


--
;-)

Avatar
Jean-Marc Bourguet
bernard tatin writes:

Fabien LE LEZ wrote:
Disons qu'un destructeur peut effectivement lancer une exception en
cas de crise grave (i.e. le programme ne peut vraiment pas continuer),
mais dans ce cas, autant appeler abort() carrément.



Dans mon programme, pas dans mon exemple, c'est vraiment le cas. Lorsque le
programme qui utilise ma classe est bien écrit, bien testé et la classe
utilisée correctement, l'exception ne devrait jamais être levée.

J'espérais que cette exception soit récupérable et que toutes les resources
allouées soient désallouées proprement. Abort et exit sont un peu plus
brutales.

Finalement, j'ai remplacé l'exception par un exit() avec un code qui
me va bien.


exit est batard (on fait une partie du clean up mais pas tout,
pourquoi? Soit la situation est assez grave pour ne pas vouloir faire
du cleanup, mais alors aucun, soit on veut le clean up, mais alors
tout) et ne devrait pas etre employe en C++ (a part naturellement
qu'on a des lib ecrites en C qui... :-()

Dans le cas d'un destructeur... que faire? Quels sont les grands
moyens de gestion d'erreur?
- retour de valeur a tester par l'appelant
- exception
- abort
et sans oublier le grand principe qu'ajouter un niveau d'indirection
est la solution universelle
- callback.

1/ Retour de valeur. Naturellement un resultat ou modifier un
parametre n'est pas possible. Mais il y a la possibilite de
positionner une variable globale (ou avec un niveau d'indirection
supplementaire un objet dont l'adresse est accessible).

2/ L'exception est possible. Peut-etre conditionnellement
(std::uncaught_exception est la pour ca) mais si c'est pour appeller
abort, autant laisser s'echapper l'exception, set_unexpected et
set_terminate laissant a l'utilisateur la possibilite d'intervenir.

3/ abort (ou mieux terminate qui a nouveau laisse la possibilite
d'agir si reellement necessaire) est evidemment une possibilite si le
cas est reellement impossible. Ou via assert(). Quel est l'utilite
de faire du cleanup si le cas ne peut resulter que d'une erreur de
programmation? Il y a au moins autant de risque que le cleanup soit
nuisible a ce qu'il y a de chance qu'il soit utile.

4/ Le callback laisse a l'utilisateur le choix entre une des methodes
ci-dessus.

A+

--
Jean-Marc, professoral aujourd'hui :-)
FAQ de fclc++: http://www.cmla.ens-cachan.fr/~dosreis/C++/FAQ
C++ FAQ Lite en VF: http://www.ifrance.com/jlecomte/c++/c++-faq-lite/index.html
Site de usenet-fr: http://www.usenet-fr.news.eu.org


Avatar
Jean-Marc Bourguet
Andre Heinen writes:

Si on appelle exit(), on court le risque d'appeler des destructeurs
qui peuvent eux-mêmes tenter d'arrêter le programme en appelant
exit(), d'où une récursion infinie.


Je n'aime pas exit() en C++, mais j'ai le souvenir que ca doit
fonctionner sans probleme.

A+

--
Jean-Marc
FAQ de fclc++: http://www.cmla.ens-cachan.fr/~dosreis/C++/FAQ
C++ FAQ Lite en VF: http://www.ifrance.com/jlecomte/c++/c++-faq-lite/index.html
Site de usenet-fr: http://www.usenet-fr.news.eu.org

Avatar
Andre Heinen
On Wed, 27 Oct 2004 13:27:35 +0200, Fabien LE LEZ
wrote:

On Wed, 27 Oct 2004 13:24:06 +0200, Andre Heinen
:

Le choix n'est pas nécessairement facile. Si on appelle exit(),
on court le risque d'appeler des destructeurs qui peuvent
eux-mêmes tenter d'arrêter le programme en appelant exit()


Et surtout, il y a des chances pour qu'on soit arrivé à une extrêmité
telle que le programme ne peut vraiment plus tourner -- même pas pour
nettoyer la mémoire.


En fait, ce n'est pas la mémoire qui me tracasse: elle sera de
toute façon libérée par le système d'exploitation. Je pense
plutôt à des fichiers à flusher. Ceci dit, comme Jean-Marc et
toi le faites remarquer, si on en arrive à une telle extrémité,
il y a des chances pour que le cleanup ne soit plus possible.

--
Andre Heinen
My address, rot13-encoded: n qbg urvara ng rhebcrnayvax qbg pbz


Avatar
Andre Heinen
On 27 Oct 2004 14:01:30 +0200, Jean-Marc Bourguet
wrote:

Andre Heinen writes:

Si on appelle exit(), on court le risque d'appeler des destructeurs
qui peuvent eux-mêmes tenter d'arrêter le programme en appelant
exit(), d'où une récursion infinie.


Je n'aime pas exit() en C++, mais j'ai le souvenir que ca doit
fonctionner sans probleme.


Si tu n'as pas de récursion infinie, ça fonctionne sans problème,
mais sans appeler les destructeurs des objets automatiques.

Quant à la récursion infinie, je sais que c'est possible, mais je
n'en ai jamais vu. Je n'ai encore jamais vu de destructeur qui
appelle exit(). Et quand bien même ce serait le cas, encore
faudrait-il que ce soient justement des instances de cette
classe-là qui soient détruites par exit().

--
Andre Heinen
My address, rot13-encoded: n qbg urvara ng rhebcrnayvax qbg pbz


Avatar
Andre Heinen
On 27 Oct 2004 13:57:21 +0200, Jean-Marc Bourguet
wrote:

Ou via assert().


Attention, assert() fonctionnera dans le code de test mais pas
dans le code de production. Il ne faut pas l'utiliser pour gérer
des erreur susceptibles de se produire après la phase de debug.

--
Andre Heinen
My address, rot13-encoded: n qbg urvara ng rhebcrnayvax qbg pbz

1 2 3 4 5