Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

Destruction des exceptions

16 réponses
Avatar
Bastien Durel
Bonjour,

J'ai un problème dans une procédure qui utilise des exceptions : je lève
une exception dans une fonction, mais mon instance est détruite avant
d'être passée au gestionnaire d'exception.

voici l'appelant :
try
{
LOG("Init parser");
_Init(lOptions, mInitialized);
LOG("Init Done");
}
catch (const TDataParserException & e)
{
LOG("Exception !" << (int)(void*)(&e));
LOG(e.Where() << ": " << e.What());
mInitialized=false;
mFile.Rollback();
}

et ici, un bout de la fonction membre _Init :
void USER::Init(const TDataOptions& aOptions, bool& aInitialized)
{
//[...]
if (!(mInputFile.SubStr(0, 3) == "UNA"))
throw TDataParserException("File does not begins with UNA",
__FILE__, __LINE__);

Et voici ce que je trouve dans ma trace :
Wed Oct 03 14:27:35 2007: Entering in
TDataParserException::TDataParserException(const char_t*, const char*,
int) (1241792)
Wed Oct 03 14:27:35 2007: TDataParserException: File does not begins
with UNA, ./edifact/user_init.cpp: 48
Wed Oct 03 14:27:35 2007: Entering in virtual
TDataParserException::~TDataParserException() (1241792)
Wed Oct 03 14:27:35 2007: Exception !76741664

le chiffre entre parenthèse est l'addresse *this
le e.Where() provoque une violation d'accès.

Pour être complet, je dois ajouter que le code appelant est dans une
bibliothèque statique, et fait partie d'une classe abstraite, _Init
étant implémentée dans une classe qui en dérive.

pseudocode :
// dans la bibliothèque
class Base {
void Init() {
try { _Init(); }
catch (const TDataParserException& e) { e.What; }
}
virtual void _Init() throw (TDataParserException)=0;
};

// Dans le source
class USER : public Base {
virtual void _Init() throw (TDataParserException) {
throw TDataParserException("");
}
};

Le tout est compilé avec g++ (GCC) 3.4.2 (mingw-special) et embarqué
dans une dll rendant l'utilisation d'un débogueur assez contraignante.

*On dirait* que l'exception est copiée avant d'être passée au
gestionnaire, mais nulle part dans ma trace n'apparait l'appel au
constructeur de copie de TDataParserException.
Avez-vous une idée de la raison de ce comportement ? Ça ne me semble pas
cohérent ...

Merci,

--
Bastien

10 réponses

1 2
Avatar
Jean-Marc Bourguet
Bastien Durel writes:

J'ai un problème dans une procédure qui utilise des exceptions : je lève
une exception dans une fonction, mais mon instance est détruite avant
d'être passée au gestionnaire d'exception.


J'ai pas lu ce qui suit, mais je me demande si tu n'as pas oublie que les
exceptions etaient copiees quand on les lancait.

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
James Kanze
On Oct 3, 3:25 pm, Bastien Durel wrote:
J'ai un problème dans une procédure qui utilise des exceptions
: je lève une exception dans une fonction, mais mon instance
est détruite avant d'être passée au gestionnaire d'exception.

voici l'appelant :
try
{
LOG("Init parser");
_Init(lOptions, mInitialized);
LOG("Init Done");
}
catch (const TDataParserException & e)
{
LOG("Exception !" << (int)(void*)(&e));
LOG(e.Where() << ": " << e.What());
mInitializedúlse;
mFile.Rollback();
}

et ici, un bout de la fonction membre _Init :
void USER::Init(const TDataOptions& aOptions, bool& aInitialized)
{
//[...]
if (!(mInputFile.SubStr(0, 3) == "UNA"))
throw TDataParserException("File does not begins with UNA",
__FILE__, __LINE__);

Et voici ce que je trouve dans ma trace :
Wed Oct 03 14:27:35 2007: Entering in
TDataParserException::TDataParserException(const char_t*, const char*,
int) (1241792)
Wed Oct 03 14:27:35 2007: TDataParserException: File does not begins
with UNA, ./edifact/user_init.cpp: 48
Wed Oct 03 14:27:35 2007: Entering in virtual
TDataParserException::~TDataParserException() (1241792)
Wed Oct 03 14:27:35 2007: Exception !76741664

le chiffre entre parenthèse est l'addresse *this
le e.Where() provoque une violation d'accès.


Ce qui est levé est (conceptuellement, au moins) une copie de
l'objet dans l'expression de throw. Si tu instrumentes le
constructeur de copie de TDataParserException, tu dois voir
qu'il est appelé aussi. (Le compilateur a le droit de supprimer
le copie. Autant que je sache, la plupart ne le font pas,
estîmant que ça ne vaut pas la peine d'optimiser les chemins
exceptionnels.)

Pour être complet, je dois ajouter que le code appelant est dans une
bibliothèque statique, et fait partie d'une classe abstraite, _Init
étant implémentée dans une classe qui en dérive.

pseudocode :
// dans la bibliothèque
class Base {
void Init() {
try { _Init(); }
catch (const TDataParserException& e) { e.What; }
}
virtual void _Init() throw (TDataParserException)=0;
};

// Dans le source
class USER : public Base {
virtual void _Init() throw (TDataParserException) {
throw TDataParserException("");
}
};


Ça ne doit rien changer.

Le tout est compilé avec g++ (GCC) 3.4.2 (mingw-special) et
embarqué dans une dll rendant l'utilisation d'un débogueur
assez contraignante.

*On dirait* que l'exception est copiée avant d'être passée au
gestionnaire, mais nulle part dans ma trace n'apparait l'appel
au constructeur de copie de TDataParserException. Avez-vous
une idée de la raison de ce comportement ? Ça ne me semble pas
cohérent ...


Le compilateur peut supprimer la copie à titre d'optimisation.
Dans ce cas-là, en revanche, il ne doit pas appeler le
destructeur avant la fin du bloc de catch. (C'est ce que fait
g++ 4.1.0.)

--
James Kanze (GABI Software) email:
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
Bastien Durel
On 03/10/2007 16:33, James Kanze wrote:
On Oct 3, 3:25 pm, Bastien Durel wrote:
J'ai un problème dans une procédure qui utilise des exceptions
: je lève une exception dans une fonction, mais mon instance
est détruite avant d'être passée au gestionnaire d'exception.

voici l'appelant :
try
{
LOG("Init parser");
_Init(lOptions, mInitialized);
LOG("Init Done");
}
catch (const TDataParserException & e)
{
LOG("Exception !" << (int)(void*)(&e));
LOG(e.Where() << ": " << e.What());
mInitializedúlse;
mFile.Rollback();
}

et ici, un bout de la fonction membre _Init :
void USER::Init(const TDataOptions& aOptions, bool& aInitialized)
{
//[...]
if (!(mInputFile.SubStr(0, 3) == "UNA"))
throw TDataParserException("File does not begins with UNA",
__FILE__, __LINE__);

Et voici ce que je trouve dans ma trace :
Wed Oct 03 14:27:35 2007: Entering in
TDataParserException::TDataParserException(const char_t*, const char*,
int) (1241792)
Wed Oct 03 14:27:35 2007: TDataParserException: File does not begins
with UNA, ./edifact/user_init.cpp: 48
Wed Oct 03 14:27:35 2007: Entering in virtual
TDataParserException::~TDataParserException() (1241792)
Wed Oct 03 14:27:35 2007: Exception !76741664

le chiffre entre parenthèse est l'addresse *this
le e.Where() provoque une violation d'accès.


Ce qui est levé est (conceptuellement, au moins) une copie de
l'objet dans l'expression de throw. Si tu instrumentes le
constructeur de copie de TDataParserException, tu dois voir
qu'il est appelé aussi. (Le compilateur a le droit de supprimer
le copie. Autant que je sache, la plupart ne le font pas,
estîmant que ça ne vaut pas la peine d'optimiser les chemins
exceptionnels.)

Je l'ai instrumenté, et ne le vois pas, justement.


Pour être complet, je dois ajouter que le code appelant est dans une
bibliothèque statique, et fait partie d'une classe abstraite, _Init
étant implémentée dans une classe qui en dérive.

pseudocode :
// dans la bibliothèque
class Base {
void Init() {
try { _Init(); }
catch (const TDataParserException& e) { e.What; }
}
virtual void _Init() throw (TDataParserException)=0;
};

// Dans le source
class USER : public Base {
virtual void _Init() throw (TDataParserException) {
throw TDataParserException("");
}
};


Ça ne doit rien changer.

Je le pensais bien, mais bon, autant donner toute l'information, quelque

fois que.

Le tout est compilé avec g++ (GCC) 3.4.2 (mingw-special) et
embarqué dans une dll rendant l'utilisation d'un débogueur
assez contraignante.

*On dirait* que l'exception est copiée avant d'être passée au
gestionnaire, mais nulle part dans ma trace n'apparait l'appel
au constructeur de copie de TDataParserException. Avez-vous
une idée de la raison de ce comportement ? Ça ne me semble pas
cohérent ...


Le compilateur peut supprimer la copie à titre d'optimisation.
Dans ce cas-là, en revanche, il ne doit pas appeler le
destructeur avant la fin du bloc de catch. (C'est ce que fait
g++ 4.1.0.)
Je l'ai vu sur mon g++ 4.2, aussi.


--
Bastien


Avatar
Bastien Durel
Si je récupère l'exception par copie plutôt que par référence, il
commence tout de même par détruire l'instance sur le tas avant de
reconstruire par recopie à partir d'une référence invalide, ce qui
provoque une violation d'accès :

Wed Oct 03 18:11:14 2007: Entering in TDataParserException::TDataParserException(const char_t*, const char*, int) (1241744)
Wed Oct 03 18:11:14 2007: Entering in const char_t* unicodeFromChar(const char*, const char*)
Wed Oct 03 18:11:14 2007: Entering in const char_t* unicodeFromChar(const char*, const char*, int)
Wed Oct 03 18:11:14 2007: TDataParserException: File does not begins with UNA, ./edifact/user_init.cpp: 48
Wed Oct 03 18:11:14 2007: mMessage is 76677424 mWhere is 96845984
Wed Oct 03 18:11:14 2007: Entering in virtual TDataParserException::~TDataParserException() (1241744)
Wed Oct 03 18:11:14 2007: Entering in TDataBuffer::~TDataBuffer() (1242928)
Wed Oct 03 18:11:14 2007: Entering in TDataParserException::TDataParserException(const TDataParserException&) (1243152)


De même si je fais un affreux "throw *(new TDataParserException());"

--
Bastien

Avatar
James Kanze
On Oct 3, 6:24 pm, Bastien Durel wrote:
Si je récupère l'exception par copie plutôt que par référence, il
commence tout de même par détruire l'instance sur le tas avant de
reconstruire par recopie à partir d'une référence invalide, ce qui
provoque une violation d'accès :

Wed Oct 03 18:11:14 2007: Entering in TDataParserException::TDataParser Exception(const char_t*, const char*, int) (1241744)
Wed Oct 03 18:11:14 2007: Entering in const char_t* unicodeFromChar(con st char*, const char*)
Wed Oct 03 18:11:14 2007: Entering in const char_t* unicodeFromChar(con st char*, const char*, int)
Wed Oct 03 18:11:14 2007: TDataParserException: File does not begins wi th UNA, ./edifact/user_init.cpp: 48
Wed Oct 03 18:11:14 2007: mMessage is 76677424 mWhere is 96845984
Wed Oct 03 18:11:14 2007: Entering in virtual TDataParserException::~TD ataParserException() (1241744)
Wed Oct 03 18:11:14 2007: Entering in TDataBuffer::~TDataBuffer() (1242 928)
Wed Oct 03 18:11:14 2007: Entering in TDataParserException::TDataParser Exception(const TDataParserException&) (1243152)


De même si je fais un affreux "throw *(new TDataParserException());"


C'est difficile à dire. J'ai essayé une version simplifiée de ce
que j'ai compris de ton code, et ça marchait bien (g++ 4.1.0,
sur Solaris/Sparc). Est-ce qu'il peut y avoir un problème dans
l'hiérarchie de TDataParserException ? Ou dans une fonction que
tu appelles dans le constructeur ? Encore que le destructeur de
TDataParserException ne doit être appelé que si le constructeur
a fini. Ou est-ce qu'il y aurait un destructeur d'un objet sur
la pile qui lève une exception ?

--
James Kanze (GABI Software) email:
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
Christophe Lephay
"James Kanze" a écrit dans le message de news:

On Oct 3, 6:24 pm, Bastien Durel wrote:
Si je récupère l'exception par copie plutôt que par référence, il
commence tout de même par détruire l'instance sur le tas avant de
reconstruire par recopie à partir d'une référence invalide, ce qui
provoque une violation d'accès :
Wed Oct 03 18:11:14 2007: Entering in
TDataParserException::TDataParserException(const TDataParserException&)
(1243152)


De même si je fais un affreux "throw *(new TDataParserException());"


C'est difficile à dire. J'ai essayé une version simplifiée de ce
que j'ai compris de ton code, et ça marchait bien (g++ 4.1.0,
sur Solaris/Sparc). Est-ce qu'il peut y avoir un problème dans
l'hiérarchie de TDataParserException ? Ou dans une fonction que
tu appelles dans le constructeur ? Encore que le destructeur de
TDataParserException ne doit être appelé que si le constructeur
a fini. Ou est-ce qu'il y aurait un destructeur d'un objet sur
la pile qui lève une exception ?


Peut-être le constructeur TDataParserException( const
TDataParserException& ) génère-t-il lui-même une exception...



Avatar
James Kanze
On Oct 4, 5:20 am, "Christophe Lephay"
wrote:
"James Kanze" a écrit dans le message de news:

On Oct 3, 6:24 pm, Bastien Durel wrote:

Si je récupère l'exception par copie plutôt que par référenc e, il
commence tout de même par détruire l'instance sur le tas avant de
reconstruire par recopie à partir d'une référence invalide, ce q ui
provoque une violation d'accès :
Wed Oct 03 18:11:14 2007: Entering in
TDataParserException::TDataParserException(const TDataParserException &)
(1243152)
De même si je fais un affreux "throw *(new TDataParserException());"

C'est difficile à dire. J'ai essayé une version simplifiée de ce

que j'ai compris de ton code, et ça marchait bien (g++ 4.1.0,
sur Solaris/Sparc). Est-ce qu'il peut y avoir un problème dans
l'hiérarchie de TDataParserException ? Ou dans une fonction que
tu appelles dans le constructeur ? Encore que le destructeur de
TDataParserException ne doit être appelé que si le constructeur
a fini. Ou est-ce qu'il y aurait un destructeur d'un objet sur
la pile qui lève une exception ?


Peut-être le constructeur TDataParserException( const
TDataParserException& ) génère-t-il lui-même une exception...


Si c'est le cas, on ne doit pas en appeler le destructeur. On
n'appelle le destructeur que sur des objets (ou sous-objets)
dont le constructeur a fini.

--
James Kanze (GABI Software) email:
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
Bastien Durel
On 03/10/2007 22:23, James Kanze wrote:
On Oct 3, 6:24 pm, Bastien Durel wrote:
Si je récupère l'exception par copie plutôt que par référence, il
commence tout de même par détruire l'instance sur le tas avant de
reconstruire par recopie à partir d'une référence invalide, ce qui
provoque une violation d'accès :

Wed Oct 03 18:11:14 2007: Entering in TDataParserException::TDataParserException(const char_t*, const char*, int) (1241744)
Wed Oct 03 18:11:14 2007: Entering in const char_t* unicodeFromChar(const char*, const char*)
Wed Oct 03 18:11:14 2007: Entering in const char_t* unicodeFromChar(const char*, const char*, int)
Wed Oct 03 18:11:14 2007: TDataParserException: File does not begins with UNA, ./edifact/user_init.cpp: 48
Wed Oct 03 18:11:14 2007: mMessage is 76677424 mWhere is 96845984
Wed Oct 03 18:11:14 2007: Entering in virtual TDataParserException::~TDataParserException() (1241744)
Wed Oct 03 18:11:14 2007: Entering in TDataBuffer::~TDataBuffer() (1242928)
Wed Oct 03 18:11:14 2007: Entering in TDataParserException::TDataParserException(const TDataParserException&) (1243152)


De même si je fais un affreux "throw *(new TDataParserException());"


C'est difficile à dire. J'ai essayé une version simplifiée de ce
que j'ai compris de ton code, et ça marchait bien (g++ 4.1.0,
sur Solaris/Sparc).
De même avec mon g++ 4.1.2 sur Linux/x86


Est-ce qu'il peut y avoir un problème dans
l'hiérarchie de TDataParserException ?
Je ne pense pas, puisque TDataParserException est la base de ma

hiérarchie d'exceptions.

Ou dans une fonction que
tu appelles dans le constructeur ?
La ligne de log récaptitulant l'exception est produite par la dernière

instruction du constructeur.

Encore que le destructeur de
TDataParserException ne doit être appelé que si le constructeur
a fini. Ou est-ce qu'il y aurait un destructeur d'un objet sur
la pile qui lève une exception ?

Pas que je sache, et d'ailleurs les objets présents sur la pile sont

détruits après (trois TDataBuffer inoffensifs), puisque construits hors
du if qui lève l'exception (si on peut encore raisonner logiquement ...)

--
Bastien



Avatar
Christophe Lephay
"James Kanze" a écrit dans le message de news:

On Oct 4, 5:20 am, "Christophe Lephay"
wrote:
Peut-être le constructeur TDataParserException( const
TDataParserException& ) génère-t-il lui-même une exception...


Si c'est le cas, on ne doit pas en appeler le destructeur. On
n'appelle le destructeur que sur des objets (ou sous-objets)
dont le constructeur a fini.


Si j'ai bien compris (rien n'est moins sur), une première exception est
correctement créée dans le throw( ... ) par un premier constructeur, puis
l'exception est recopiée via le constructeur TDataParserException( const
TDataParserException& ).

Si le second constructeur lève une exception, le destructeur est tout de
même appelé sur l'exception construite initialement.


Avatar
Christophe Lephay
"Christophe Lephay" a écrit dans le message
de news: 47050bb7$0$27381$
"James Kanze" a écrit dans le message de news:

On Oct 4, 5:20 am, "Christophe Lephay"
wrote:
Peut-être le constructeur TDataParserException( const
TDataParserException& ) génère-t-il lui-même une exception...


Si c'est le cas, on ne doit pas en appeler le destructeur. On
n'appelle le destructeur que sur des objets (ou sous-objets)
dont le constructeur a fini.


Si j'ai bien compris (rien n'est moins sur), une première exception est
correctement créée dans le throw( ... ) par un premier constructeur, puis
l'exception est recopiée via le constructeur TDataParserException( const
TDataParserException& ).

Si le second constructeur lève une exception, le destructeur est tout de
même appelé sur l'exception construite initialement.


En fait non : en relisant, le destructeur est appelé avant la recopie...



1 2