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

Avatar
drkm
Laurent Deniau writes:

bernard tatin wrote:

Je pressentais bien qu'une excception dans un destructeur n'était
pas une bonne idée,


pas forcement.


Sans doute. Je ne vois pas de cas particulier où cela se
justifierait, mais je manque d'imaginiation.

mais je ne trouvais rien à ce sujet.


voir uncaught_exception().


Mais si je comprend bien, lorsqu'une exception est levée, les
destructeurs des objets automatiques dans la portée sont appelés,
*avant* le gestionnaire d'exception (le cas du PO) ou
`uncaught_exception()'.

Cfr. la conclusion de <URL:http://www.gotw.ca/gotw/047.htm> :

3. Is there any other good use for uncaught_exception? Discuss and
draw conclusions.

Unfortunately, I do not know of any good and safe use for
std::uncaught_exception. My advice: Don't use it.

--drkm


Avatar
Fabien LE LEZ
On Wed, 27 Oct 2004 17:41:51 +0200, bernard tatin
:

Depuis tout à l'heure, je viens de trouver deux autres configurations
qui me mettent dans cette situation.


De deux choses l'une :
- soit une fonction appelée dans un destructeur peut
légitimement lancer une exception, auquel cas le destructeur doit
comporter un bloc try...catch
- soit l'exception lancée par le destructeur vient d'une erreur
de programmation, et le fait que le programme s'arrête carrément n'est
pas gênant, car ce bug n'existera plus à la fin de la phase de
développement.


--
;-)

Avatar
Fabien LE LEZ
On Wed, 27 Oct 2004 18:27:15 +0200, drkm :

Unfortunately, I do not know of any good and safe use for
std::uncaught_exception. My advice: Don't use it.


En lisant Herb Sutter, on s'aperçoit qu'un sacré paquet de
fonctionnalités de C++ sont inutiles[*] ;-)

[*] à commencer par "export", même s'il est moins catégorique sur cet
exemple.

--
;-)

Avatar
Loïc Joly
Fabien LE LEZ wrote:

On Wed, 27 Oct 2004 15:10:53 +0200, bernard tatin
:


L'exit est suffisament brutal pour que le serveur de
socket d'en face soit souvent obligé de ne réagir que sur un time-out.



D'un autre côté, c'est sensé n'arriver qu'exceptionnellement, dans des
cas où il n'est même pas sûr que l'OS soit encore dans un état assez
stable pour fermer une connexion TCP.


En fait, il y a deux écoles :
- Ceux qui disent que l'on ne peut plus rien prévoir, et alors, autant
tout arrêter au plus vite.
- Ceux qui disent que malgré tout, il y a une chance que ça fasse quand
même quelquechose de correct, alors autant tenter de le faire, même si
ça peut donner une fausse impression de sécurité.

--
Loïc


Avatar
Fabien LE LEZ
On Wed, 27 Oct 2004 20:47:10 +0200, Loïc Joly
:

Ceux qui disent que malgré tout, il y a une chance que ça fasse quand
même quelquechose de correct, alors autant tenter de le faire


... en essayant de s'assurer tout de même que ça ne fera pas quelque
chose de catastrophique :/


--
;-)

Avatar
bernard tatin
Jean-Marc Bourguet wrote:

Un timeout de 2 minutes sur le serveur de temps en temps pendant la
periode de developpement (puisque ca ne devrait jamais arriver) pose
probleme? C'est quoi votre structure?

A+

Mon (petit) projet c'est d'abord me remettre au C++ que je n'ai pas

vraiment pratiqué depuis quelques années. J'ai perdu un peu la main
d'autant plus que j'ai fait beaucoup de Java (pour manger) et de Lisp
(pour jouer). Ensuite, c'est tenter de maîtriser la programmation réseau
sous Unix (MacOS X, mais ça marche aussi sous FreeBSD et un peu Linux,
mais là je ne peux pas bien tester). Je fais des choses qui existent et
qui marchent mieux ailleurs, mais je reprend point par point la
programmation réseau et j'apprends. J'apprends la manipulation des noms
d'hôtes, des protocoles autres que TCP et UDP... à construire une trame
IP complète et l'envoyer...

J'ai un premier ensemble de classes pour traiter le protocole ICMP. Je
cherche à gérer toutes les erreurs par des exceptions, d'où mes
déconvenues premières.

J'ai un second ensemble de classes pour un protocole d'échange de
données en TCP. Je ne sais pas si j'arriverais au bout, le temps dont je
dispose n'est pas extensible, mais le but est de faire un ensemble de
classes pour du calcul distribué sur plusieurs machine - je sais que
c'est ambitieux. Je ne ferais pas mieux ce que d'autres ont déjà fait,
mais en le faisant j'ai déjà (ré)appris beaucoup de choses. Dans le cas
de ce système client serveur, j'essaie de limiter le plus possible
l'utilisation des resources côté communication. Et ces dernières heures,
avec mes tests, j'ai pu voir que la gestion des erreurs sur les sockets
est vraiment complexe. A quel moment puis-je reprendre la communication,
à quel moment suis-je obliger de couper et de recommencer ? J'avais
beaucoup espéré sur mon exception lancée depuis le destructeur. Je dois
revoir mon architecture pour m'en dépétrer.

Voilà donc l'histoire complète.

Bernard.

Avatar
Gabriel Dos Reis
Fabien LE LEZ writes:

| On Wed, 27 Oct 2004 18:27:15 +0200, drkm :
|
| > Unfortunately, I do not know of any good and safe use for
| > std::uncaught_exception. My advice: Don't use it.
|
| En lisant Herb Sutter, on s'aperçoit qu'un sacré paquet de
| fonctionnalités de C++ sont inutiles[*] ;-)
|
| [*] à commencer par "export", même s'il est moins catégorique sur cet
| exemple.

Je regrette que le dernier XC++ soit encore aussi anti-export. :-(

-- Gaby
Avatar
kanze
Andre Heinen wrote in message
news:...
On 27 Oct 2004 15:18:09 +0200, Jean-Marc Bourguet
wrote:

Depuis quand on enleve les gilets de sauvetage quand on part en mer?


Si tu refuses d'enlever ton gilet de sauvetage quand tu pars en mer,
il est d'autant plus important de ne jamais utiliser assert(),
puisqu'elle ne sera pas compilée dans les versions release...


Selon la norme, assert serait compilé sauf si le macro NDEBUG est
défini. C'est à toi de décider si c'est défini dans les les versions de
release ou non. (C'est effectivement défini par défaut dans les
makefiles pour le build release sous Visual Studios. Mais c'est trivial
de les changer -- et puisque de toute façon, tu vas vérifier que tout
est comme tu veux pour la release, tu le changes dans la foulée.)

En fait, dans des projets sérieux, je me suis prèsque toujours servi de
mon propre assert. Afin de définir mes messages d'erreur moi-même,
m'assurer qu'ils aillent vers le log, etc. Mais la principe est la même
(et je me servais de NDEBUG de la même façon que dans assert).

Personnellement il m'est arrivé de coder des assert() qui
ralentissaient considérablement le programme. Dans ce cas, c'est un
mécanisme pratique. Quand tu testes, tant pis si le programme est plus
lent: tu veux tout vérifier. A partir du moment où tu as le sentiment
de ne plus (trop) courir de risque, tu définis NDEBUG, les assert()
disparaissent, et le code devient suffisamment rapide pour être mis en
production.


Il arrive des cas où effectivement, le profiler montre qu'il faut virer
l'assert() (ou tout autre code de vérification). Dans ce cas-là (et
seulement dans ce cas-là), on écrit quelque chose du genre :

#ifdef PRODUCTION
#define NDEBUG
#endif
#include <assert.h>

// Fonction avec le code critique...

#undef NDEBUG
#include <assert.h>

Si le comité C a exigé que l'inclusion multiple de <assert.h> fonction,
il y a bien une raison.

--
James Kanze GABI Software http://www.gabi-soft.fr
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34


Avatar
kanze
Jean-Marc Bourguet wrote in message
news:...
Andre Heinen writes:

On 27 Oct 2004 15:18:09 +0200, Jean-Marc Bourguet
wrote:

Depuis quand on enleve les gilets de sauvetage quand on part en mer?


Si tu refuses d'enlever ton gilet de sauvetage quand tu pars en mer,
il est d'autant plus important de ne jamais utiliser assert(),
puisqu'elle ne sera pas compilée dans les versions release...


Je viens de verifier, le code que nous livrons n'est pas compile avec
-DNDEBUG et il me semblait que c'etait une pratique courante.


Ça a été la pratique courante partout où j'ai travaillé, en tout cas.

--
James Kanze GABI Software http://www.gabi-soft.fr
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34



Avatar
kanze
Marc Boyer wrote in message
news:<clobap$4f9$...

Ca, ça montre surtout que assert est trop simpliste pour un
contexte professionnel.


Pourquoi?


Pour moi, une macro chargée de vérifier des invariants devrait
1) donner plus d'info que le simple couple __LINE__ / __FILE__
par défaut (__fun__ aussi, puis un numéro de version)


Ce n'est pas l'absense de __fun__ (par exemple) qui me gène. C'est que
le message ne peut pas s'adopter selon la destination. Donc, par
exemple, si c'est quelque part où l'utilisateur le voit (ce qui n'est
pas sans intérêt), ce qu'il faut, c'est quelque chose du genre :

INTERNAL : veuillez contacter le service après-vente, en leur
envoyant le fichier core que tu trouveras dans ...,
ainsi que les informations suivantes :

produit : ...
version : ...

ainsi qu'une description de ce que vous étiez en
train de faire.

Si j'ai le core, je n'ai pas besoin de __LINE__/__FILE__. En revanche,
__LINE__, __FILE__ et même __fun__ ne me servent à rien si je ne connais
pas la version.

Évidemment, dans les applications internes, le message est plus simple,
simplement un texte pour dire qu'il y a eu un problème, et qu'il faut
rédémarrer le programme. Parce que le macro aurait envoyé un email avec
les informations pertinantes à la bonne adresse tout seul. (Dans la
plupart des applications, l'application que voit l'utilisateur n'est en
fait qu'un script assez simple, qui reconnaît que le programme est
sortie d'une façon anormale, et le rélance. Et c'est le script qui
envoie l'email, aussi, en y incorporant le core.)

2) permette d'activer/désactiver de manière plus fine
les invariant (notion de groupe et d'identité), permettre
des choses du genre
gcc -DNO_PRECOND(foo)
pour virer les preconditions de la fonction foo
gcc -DNO_GROUP_PRECOND(overflow)
pour virer globalement les tests d'overflow


Là ce que tu démande, ce n'est plus simplement assert, mais un système
complet de gestion des log. Où, d'ailleurs, c'est plus intéressant
d'avoir une configuration dynamique -- pour résoudre un problème chez un
client, tu vas avoir besoin peut-être des traces supplémentaire, mais tu
ne veux certainement pas installer un nouveau binaire. (Dans le cas des
serveurs, je m'arrange même en général pour pouvoir changer la
configuration de trace sans arrêter le serveur.)

3) le fait de simplement écrire dans stderr n'est pas
forcément ce qu'on attend, on peut vouloir un peu
plus, non ?


Surtout dans le cas des serveurs, où stderr est attaché à /dev/null.

Ce genre de choses, non ?


N'importe quelle application sérieuse aurait une gestion de log
configurable. En général, c'est effectivement intéressant que assert s'y
integre.

Dans la pratique (et je sais que c'est formellement un comportement
indéfini), on peut fournir son propre fichier assert.h, de façon à
ratrapper après coup. Dans les grands projets (c-à-d, ceux qui sont
reconnu grand dès le départ), on définit une gestion de log avant de
commencer, dont on se sert à la place d'assert.

--
James Kanze GABI Software http://www.gabi-soft.fr
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34