A ce sujet une question sur le comportement de CodeGuard,
une extension de C++ Builder qui permet de tracker les
fuites mémoires...
Quand je construis un objet avec new et qu'une exception est
lancée dans le constructeur, à la sortie du programme,
CodeGuard m'indique une fuite mémoire parce que l'objet en
question n'a pas été détruit.
Je crois bien me souvenir qu'il n'est pas besoin de faire un
delete dans ce cas.
Si je comprend bien la question, C++Builder a raison.
C'est à toi de détruire l'objet par delete.
La façon de faire lorsque tu crains qu'une exception soit
levée dans ton constructeur est de mettre le code dans un bloc
try/catch. Lorsque tu captures l'exception tu effaces l'objet,
puis tu relances l'exception.
try
{
// du code
objet* = new.....
// du code qui lève une exception
}
catch(...)
{
delete objet;
A ce sujet une question sur le comportement de CodeGuard,
une extension de C++ Builder qui permet de tracker les
fuites mémoires...
Quand je construis un objet avec new et qu'une exception est
lancée dans le constructeur, à la sortie du programme,
CodeGuard m'indique une fuite mémoire parce que l'objet en
question n'a pas été détruit.
Je crois bien me souvenir qu'il n'est pas besoin de faire un
delete dans ce cas.
Si je comprend bien la question, C++Builder a raison.
C'est à toi de détruire l'objet par delete.
La façon de faire lorsque tu crains qu'une exception soit
levée dans ton constructeur est de mettre le code dans un bloc
try/catch. Lorsque tu captures l'exception tu effaces l'objet,
puis tu relances l'exception.
try
{
// du code
objet* = new.....
// du code qui lève une exception
}
catch(...)
{
delete objet;
A ce sujet une question sur le comportement de CodeGuard,
une extension de C++ Builder qui permet de tracker les
fuites mémoires...
Quand je construis un objet avec new et qu'une exception est
lancée dans le constructeur, à la sortie du programme,
CodeGuard m'indique une fuite mémoire parce que l'objet en
question n'a pas été détruit.
Je crois bien me souvenir qu'il n'est pas besoin de faire un
delete dans ce cas.
Si je comprend bien la question, C++Builder a raison.
C'est à toi de détruire l'objet par delete.
La façon de faire lorsque tu crains qu'une exception soit
levée dans ton constructeur est de mettre le code dans un bloc
try/catch. Lorsque tu captures l'exception tu effaces l'objet,
puis tu relances l'exception.
try
{
// du code
objet* = new.....
// du code qui lève une exception
}
catch(...)
{
delete objet;
Alain Gaillard wrote:A ce sujet une question sur le comportement de CodeGuard, une
extension de C++ Builder qui permet de tracker les fuites mémoires...
Quand je construis un objet avec new et qu'une exception est lancée
dans le constructeur, à la sortie du programme, CodeGuard m'indique
une fuite mémoire parce que l'objet en question n'a pas été détruit.
Je crois bien me souvenir qu'il n'est pas besoin de faire un delete
dans ce cas.
Si je comprend bien la question, C++Builder a raison.
C'est à toi de détruire l'objet par delete.
La façon de faire lorsque tu crains qu'une exception soit levée dans ton
constructeur est de mettre le code dans un bloc try/catch. Lorsque tu
captures l'exception tu effaces l'objet, puis tu relances l'exception.
try
{
// du code
objet* = new.....
// du code qui lève une exception
}
catch(...)
{
delete objet;
throw;
}
Comment tu peux faire ça ? J'ai testé ça :
#include <cassert>
#include <exception>
struct error : public std::exception
{
};
struct Test
{
Test()
: i( 10 )
{
throw error();
};
int i;
};
struct Test1
{
Test1()
: i( 0 )
, t()
{
}
int i;
Test t;
};
int main()
{
Test1 *t = 0;
try
{
t = new Test1();
}
catch( error )
{
assert( t );
delete t;
}
return 0;
}
Et j'obtiens:
construct_leak: construct_leak.cc:40: int main(): Assertion `t' failed.
... donc comment libérer l'objet alors que je n'ai pas un pointeur
dessus ?
tableau "automatique" (i.e. alloué dans la pile) que seul l'objet levant
l'exception est détruit.
Alain Gaillard wrote:
A ce sujet une question sur le comportement de CodeGuard, une
extension de C++ Builder qui permet de tracker les fuites mémoires...
Quand je construis un objet avec new et qu'une exception est lancée
dans le constructeur, à la sortie du programme, CodeGuard m'indique
une fuite mémoire parce que l'objet en question n'a pas été détruit.
Je crois bien me souvenir qu'il n'est pas besoin de faire un delete
dans ce cas.
Si je comprend bien la question, C++Builder a raison.
C'est à toi de détruire l'objet par delete.
La façon de faire lorsque tu crains qu'une exception soit levée dans ton
constructeur est de mettre le code dans un bloc try/catch. Lorsque tu
captures l'exception tu effaces l'objet, puis tu relances l'exception.
try
{
// du code
objet* = new.....
// du code qui lève une exception
}
catch(...)
{
delete objet;
throw;
}
Comment tu peux faire ça ? J'ai testé ça :
#include <cassert>
#include <exception>
struct error : public std::exception
{
};
struct Test
{
Test()
: i( 10 )
{
throw error();
};
int i;
};
struct Test1
{
Test1()
: i( 0 )
, t()
{
}
int i;
Test t;
};
int main()
{
Test1 *t = 0;
try
{
t = new Test1();
}
catch( error )
{
assert( t );
delete t;
}
return 0;
}
Et j'obtiens:
construct_leak: construct_leak.cc:40: int main(): Assertion `t' failed.
... donc comment libérer l'objet alors que je n'ai pas un pointeur
dessus ?
tableau "automatique" (i.e. alloué dans la pile) que seul l'objet levant
l'exception est détruit.
Alain Gaillard wrote:A ce sujet une question sur le comportement de CodeGuard, une
extension de C++ Builder qui permet de tracker les fuites mémoires...
Quand je construis un objet avec new et qu'une exception est lancée
dans le constructeur, à la sortie du programme, CodeGuard m'indique
une fuite mémoire parce que l'objet en question n'a pas été détruit.
Je crois bien me souvenir qu'il n'est pas besoin de faire un delete
dans ce cas.
Si je comprend bien la question, C++Builder a raison.
C'est à toi de détruire l'objet par delete.
La façon de faire lorsque tu crains qu'une exception soit levée dans ton
constructeur est de mettre le code dans un bloc try/catch. Lorsque tu
captures l'exception tu effaces l'objet, puis tu relances l'exception.
try
{
// du code
objet* = new.....
// du code qui lève une exception
}
catch(...)
{
delete objet;
throw;
}
Comment tu peux faire ça ? J'ai testé ça :
#include <cassert>
#include <exception>
struct error : public std::exception
{
};
struct Test
{
Test()
: i( 10 )
{
throw error();
};
int i;
};
struct Test1
{
Test1()
: i( 0 )
, t()
{
}
int i;
Test t;
};
int main()
{
Test1 *t = 0;
try
{
t = new Test1();
}
catch( error )
{
assert( t );
delete t;
}
return 0;
}
Et j'obtiens:
construct_leak: construct_leak.cc:40: int main(): Assertion `t' failed.
... donc comment libérer l'objet alors que je n'ai pas un pointeur
dessus ?
tableau "automatique" (i.e. alloué dans la pile) que seul l'objet levant
l'exception est détruit.
Alain Gaillard wrote:
Normal. Il n'a pas été construit non plus. C'est bien à toi
d'assurer qu'il n'y a pas de fuites dans le constructeur quand
il sort par une exception.
De la mémoire allouée par le new, non. De toute façon, tu ne
pourrais pas, vu que tu n'en a pas le pointeur.
Non, non et non. Tu ne peux pas appeler delete sur l'objet,
parce que 1) il est illégal d'appeler delete sur un objet dont
le constructeur n'a pas terminé, et 2) tu n'en as pas l'adresse.
Mais la question concernait le cas où c'est le constructeur de
l'objet qui a levé l'exception. Dans ce cas-là, tu n'as rien à
faire.
Et tu ne pourrais rien faire, parce que tu n'as jamais eu
l'adresse renvoyée par la fonction operator new.
Ce qu'il faut, en revanche, c'est que ton constructeur soit
propre. Quelque chose comme :
Classe::Classe()
: pointerMember( new XXX )
{
if ( whatever ) {
throw SomeError ;
}
}
provoque bien une fuite de mémoire. Dans ce cas-ci
(ultra-simple), on pourrait bien insérer un « delete
pointerMember » avant le throw, mais dans le cas général, il
est conseillé d'utiliser des objets supplémentaires (pointeurs
intelligents, sous-classes, etc.) qui s'occuperont
automatiquement du nettoyage ; le compilateur appelle bien le
destructeur de tous les sous-objets dont le constructeur a fini.
Alain Gaillard wrote:
Normal. Il n'a pas été construit non plus. C'est bien à toi
d'assurer qu'il n'y a pas de fuites dans le constructeur quand
il sort par une exception.
De la mémoire allouée par le new, non. De toute façon, tu ne
pourrais pas, vu que tu n'en a pas le pointeur.
Non, non et non. Tu ne peux pas appeler delete sur l'objet,
parce que 1) il est illégal d'appeler delete sur un objet dont
le constructeur n'a pas terminé, et 2) tu n'en as pas l'adresse.
Mais la question concernait le cas où c'est le constructeur de
l'objet qui a levé l'exception. Dans ce cas-là, tu n'as rien à
faire.
Et tu ne pourrais rien faire, parce que tu n'as jamais eu
l'adresse renvoyée par la fonction operator new.
Ce qu'il faut, en revanche, c'est que ton constructeur soit
propre. Quelque chose comme :
Classe::Classe()
: pointerMember( new XXX )
{
if ( whatever ) {
throw SomeError ;
}
}
provoque bien une fuite de mémoire. Dans ce cas-ci
(ultra-simple), on pourrait bien insérer un « delete
pointerMember » avant le throw, mais dans le cas général, il
est conseillé d'utiliser des objets supplémentaires (pointeurs
intelligents, sous-classes, etc.) qui s'occuperont
automatiquement du nettoyage ; le compilateur appelle bien le
destructeur de tous les sous-objets dont le constructeur a fini.
Alain Gaillard wrote:
Normal. Il n'a pas été construit non plus. C'est bien à toi
d'assurer qu'il n'y a pas de fuites dans le constructeur quand
il sort par une exception.
De la mémoire allouée par le new, non. De toute façon, tu ne
pourrais pas, vu que tu n'en a pas le pointeur.
Non, non et non. Tu ne peux pas appeler delete sur l'objet,
parce que 1) il est illégal d'appeler delete sur un objet dont
le constructeur n'a pas terminé, et 2) tu n'en as pas l'adresse.
Mais la question concernait le cas où c'est le constructeur de
l'objet qui a levé l'exception. Dans ce cas-là, tu n'as rien à
faire.
Et tu ne pourrais rien faire, parce que tu n'as jamais eu
l'adresse renvoyée par la fonction operator new.
Ce qu'il faut, en revanche, c'est que ton constructeur soit
propre. Quelque chose comme :
Classe::Classe()
: pointerMember( new XXX )
{
if ( whatever ) {
throw SomeError ;
}
}
provoque bien une fuite de mémoire. Dans ce cas-ci
(ultra-simple), on pourrait bien insérer un « delete
pointerMember » avant le throw, mais dans le cas général, il
est conseillé d'utiliser des objets supplémentaires (pointeurs
intelligents, sous-classes, etc.) qui s'occuperont
automatiquement du nettoyage ; le compilateur appelle bien le
destructeur de tous les sous-objets dont le constructeur a fini.
Pierre Barbier de Reuille writes:Non, le assert définit dans assert.h ou cassert par la norme
spécifie bien que si la macro NDEBUG n'est pas définit au
moment de l'inclusion du fichier d'entête, alors la macro ne
fait rien.
C'est l'inverse.Or, NDEBUG est la macro qui est définit pour indiquer une
compilation en mode debug. Ca n'a rien de compilateur
spécifique ...
Aucun des 6 compilateurs que j'utilise plus ou moins
régulièrement (gcc, como, intel pour Linux, Sun CC pour
Solaris, HP aCC pour HP-UX, IBM xlC pour AIX) ne lie la
définition de NDEBUG ni à la présence des informations de
débuggage, ni au degré d'optimisation.
Pierre Barbier de Reuille <p.barbierdereuille@free.fr> writes:
Non, le assert définit dans assert.h ou cassert par la norme
spécifie bien que si la macro NDEBUG n'est pas définit au
moment de l'inclusion du fichier d'entête, alors la macro ne
fait rien.
C'est l'inverse.
Or, NDEBUG est la macro qui est définit pour indiquer une
compilation en mode debug. Ca n'a rien de compilateur
spécifique ...
Aucun des 6 compilateurs que j'utilise plus ou moins
régulièrement (gcc, como, intel pour Linux, Sun CC pour
Solaris, HP aCC pour HP-UX, IBM xlC pour AIX) ne lie la
définition de NDEBUG ni à la présence des informations de
débuggage, ni au degré d'optimisation.
Pierre Barbier de Reuille writes:Non, le assert définit dans assert.h ou cassert par la norme
spécifie bien que si la macro NDEBUG n'est pas définit au
moment de l'inclusion du fichier d'entête, alors la macro ne
fait rien.
C'est l'inverse.Or, NDEBUG est la macro qui est définit pour indiquer une
compilation en mode debug. Ca n'a rien de compilateur
spécifique ...
Aucun des 6 compilateurs que j'utilise plus ou moins
régulièrement (gcc, como, intel pour Linux, Sun CC pour
Solaris, HP aCC pour HP-UX, IBM xlC pour AIX) ne lie la
définition de NDEBUG ni à la présence des informations de
débuggage, ni au degré d'optimisation.
Pierre Barbier de Reuille wrote:kanze wrote:Pierre Barbier de Reuille wrote:Michael wrote:Pierre Barbier de Reuille wrote
in news:44e2590c $0$18265$:
[...]
Je dois dire que je ne suis pas d'accord (même si, comme tu le
dis plus loin, c'est une affaire de jugement). Mais je ne
pense pas qu'il soit bon de mélanger code de retour et
exceptions.
Certainement pas. Certains types d'erreur sont rapportés par
des exceptions, d'autres par des codes de retour. Il n'y a pas
de mélange. (Il y a aussi, bien que moins souvent, des cas
particuliers où d'autres solutions s'imposent. Genre iostream,
avec un état dans l'objet.)Un des objectif des exceptions est de ne gérer les erreurs
qu'à l'endroit où tu peux y faire quelque chose ...
L'objectif des exceptions, c'est de pouvoir propager une erreur
assez loin sans que les niveaux intermédiaires aient à s'en
occuper (directement, en tout cas), avec un nettoyage approprié.
Si de par sa nature, l'erreur ne doit pas propager loin,
l'intérêt des exceptions se trouve grandement réduit. On en paie
le coût (en lourdeur, par exemple) pour rien.
En règle générale, on ne se sert d'une exception que quand on
est certain que l'erreur ne peut pas été traitée localement.
Dans la doute, on préfère le code de retour, parce que c'est
bien plus simple et plus léger, et c'est simple aussi pour le
client de le convertir en exception s'il ne peut pas le traiter
localement.
alors qu'avec les codes de retour, il faut propager les
erreurs à la main jusqu'à l'endroit qui peut gérer les
erreurs.
Justement. Il y a beaucoup d'erreurs qu'on traite localement, et
qu'on ne propage pas. Rien n'est gratuit, et c'est bien plus
pénible d'essayer à attrapper des exceptions que de simplement
tester un code de retour.Et, typiquement, les erreurs de saisies nécessiteront une
re-saisie des données et l'erreur sera donc gérées au mieux au
niveau de l'interface graphique (pour pouvoir redemander la
saisie).
Exactement. Typiquement, la saisie se trouve dans une boucle,
avec rupture de boucle quand elle est bonne ou quand
l'utilisateur démande un abort. Et c'est bien plus simple de
gérer une boucle avec un code de retour qu'avec une exception.
Si tu utilises un code de retour pour ça, ce sera très lourd à
gérer.
Je dirais le contraire. Si tu utilises une exception pour ça, ce
sera très lourd à gérer. (En Java, je n'avais pas toujours le
choix, et je t'assure, les exceptions, dans des cas comme
l'échec d'une ouverture de fichier, ça alourdit le code
énormement.)Pour moi, le seul cas où un code de retour est utilisable dans
un système utilisant les exceptions, est s'il est acceptable
d'avoir une erreur "silencieuse".
Sauf que si tu ne testes pas le code de retour, il y a une
avorte, non ? Au moins, les codes de retour que j'ai utilisé
ont en général un drappeau « lu », et un assert qu'il soit
vrai dans le destructeur.Par exemple, dans le cas d'un overflow numérique, le fait
d'avoir NaN ou inf à la place d'un nombre valide peut se
justifier, car il est probable que le client remplace ces
valeurs par autre chose par la suite ... mais c'est un des
rares exemples que je vois comme ça.
C'est un cas particulier où il est permis de différer le
traitement d'erreur. Les erreurs des iostream sont pareilles.
Dans les deux cas, il se trouve que l'objet (le virgule flottant
ou le flux) peut devenir invalid à la suite de prèsque toute
opération. Tester après chaque opération serait assez
fastidieux, et on a choisi l'option de propager l'invalidité à
travers toutes les opérations suivantes, pour pouvoir ne le
tester qu'une fois, à la fin.
Comme tu dis, de tels cas ne sont pas fréquents.De plus, je ne vois vraiment pas en quoi le code de retour
permet, plus que les exceptions, de récupérer l'exécution (ni
plus ni moins d'ailleurs ...).
C'est beaucoup moins lourd :
if ( ! premierOperation() ) {
// gestion d'erreur...
} else if ( ! secondOperation() ) {
// gestion d'erreur (différente du précédante) ...
}
contre :
bool succeeded = false ;
try {
premierOperation() ;
succeeded = true ;
} catch ( ... ) {
// gestion d'erreur...
}
if ( succeeded ) {
try {
secondOperation() ;
} catch ( ... ) {
// gestion d'erreur (différente du précédante) ...
}
}
ou :
while ( ! end && getData() ) {
// traitement des données ...
}
if ( ! end ) {
// traitement de l'erreur ...
}
contre :
while ( ! end ) {
try {
getData() ;
// traitement des données...
} catch( ... ) {
// traitement de l'erreur...
end = true ;
}
}Il faut aussi savoir que la gestion de code de retour est
coûteuse en temps de calcul tout le temps alors que la gestion
des exceptions ne coûte que quand une exception est levée ...
Tu as des mesures ? Jusqu'ici, je n'ai jamais rencontré une
application où le choix aurait fait une différence mesurable.
[...]-- Assert (avec avortement en cas d'erreur) : tout ce qui
relève de l'erreur logiciel (y compris dans le code client)
ou d'une panne matérielle qui empêcherait au programme de
fonctionner.
Je suis d'accord pour l'erreur logiciel mais pas pour l'erreur
matérielle ! Lors d'une erreur matériel, une exception est
appropriée, quitte à simplement avertir l'utilisateur et
quitter ...
Tout dépend de l'erreur (et de l'application). Si l'erreur
matérielle est sur une périphérique sécondaire, tu as
probablement raison. Si c'est une faute de parité mémoire, en
revanche, ton programme est corrompu, et tout ce que tu fais de
plus pourrait faire encore plus de dégats. (Mais j'aurais dû
être plus précis.
Le problème de la gestion d'une panne matérielle
avec un assert et qu'il n'y aura plus aucune erreur de gérées
dès que le code est mis en production (i.e. donc plus en
debug).
Depuis quand ? Je n'ai jamais livré du code sans les assert ?
On *peut* supprimer les assert en production, si le profiler
montre qu'il pose un véritable problème de temps, mais c'est une
pratique exceptionnelle.
--
James Kanze GABI Software
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
Pierre Barbier de Reuille wrote:
kanze wrote:
Pierre Barbier de Reuille wrote:
Michael wrote:
Pierre Barbier de Reuille <p.barbierdereuille@free.fr> wrote
in news:44e2590c $0$18265$636a55ce@news.free.fr:
[...]
Je dois dire que je ne suis pas d'accord (même si, comme tu le
dis plus loin, c'est une affaire de jugement). Mais je ne
pense pas qu'il soit bon de mélanger code de retour et
exceptions.
Certainement pas. Certains types d'erreur sont rapportés par
des exceptions, d'autres par des codes de retour. Il n'y a pas
de mélange. (Il y a aussi, bien que moins souvent, des cas
particuliers où d'autres solutions s'imposent. Genre iostream,
avec un état dans l'objet.)
Un des objectif des exceptions est de ne gérer les erreurs
qu'à l'endroit où tu peux y faire quelque chose ...
L'objectif des exceptions, c'est de pouvoir propager une erreur
assez loin sans que les niveaux intermédiaires aient à s'en
occuper (directement, en tout cas), avec un nettoyage approprié.
Si de par sa nature, l'erreur ne doit pas propager loin,
l'intérêt des exceptions se trouve grandement réduit. On en paie
le coût (en lourdeur, par exemple) pour rien.
En règle générale, on ne se sert d'une exception que quand on
est certain que l'erreur ne peut pas été traitée localement.
Dans la doute, on préfère le code de retour, parce que c'est
bien plus simple et plus léger, et c'est simple aussi pour le
client de le convertir en exception s'il ne peut pas le traiter
localement.
alors qu'avec les codes de retour, il faut propager les
erreurs à la main jusqu'à l'endroit qui peut gérer les
erreurs.
Justement. Il y a beaucoup d'erreurs qu'on traite localement, et
qu'on ne propage pas. Rien n'est gratuit, et c'est bien plus
pénible d'essayer à attrapper des exceptions que de simplement
tester un code de retour.
Et, typiquement, les erreurs de saisies nécessiteront une
re-saisie des données et l'erreur sera donc gérées au mieux au
niveau de l'interface graphique (pour pouvoir redemander la
saisie).
Exactement. Typiquement, la saisie se trouve dans une boucle,
avec rupture de boucle quand elle est bonne ou quand
l'utilisateur démande un abort. Et c'est bien plus simple de
gérer une boucle avec un code de retour qu'avec une exception.
Si tu utilises un code de retour pour ça, ce sera très lourd à
gérer.
Je dirais le contraire. Si tu utilises une exception pour ça, ce
sera très lourd à gérer. (En Java, je n'avais pas toujours le
choix, et je t'assure, les exceptions, dans des cas comme
l'échec d'une ouverture de fichier, ça alourdit le code
énormement.)
Pour moi, le seul cas où un code de retour est utilisable dans
un système utilisant les exceptions, est s'il est acceptable
d'avoir une erreur "silencieuse".
Sauf que si tu ne testes pas le code de retour, il y a une
avorte, non ? Au moins, les codes de retour que j'ai utilisé
ont en général un drappeau « lu », et un assert qu'il soit
vrai dans le destructeur.
Par exemple, dans le cas d'un overflow numérique, le fait
d'avoir NaN ou inf à la place d'un nombre valide peut se
justifier, car il est probable que le client remplace ces
valeurs par autre chose par la suite ... mais c'est un des
rares exemples que je vois comme ça.
C'est un cas particulier où il est permis de différer le
traitement d'erreur. Les erreurs des iostream sont pareilles.
Dans les deux cas, il se trouve que l'objet (le virgule flottant
ou le flux) peut devenir invalid à la suite de prèsque toute
opération. Tester après chaque opération serait assez
fastidieux, et on a choisi l'option de propager l'invalidité à
travers toutes les opérations suivantes, pour pouvoir ne le
tester qu'une fois, à la fin.
Comme tu dis, de tels cas ne sont pas fréquents.
De plus, je ne vois vraiment pas en quoi le code de retour
permet, plus que les exceptions, de récupérer l'exécution (ni
plus ni moins d'ailleurs ...).
C'est beaucoup moins lourd :
if ( ! premierOperation() ) {
// gestion d'erreur...
} else if ( ! secondOperation() ) {
// gestion d'erreur (différente du précédante) ...
}
contre :
bool succeeded = false ;
try {
premierOperation() ;
succeeded = true ;
} catch ( ... ) {
// gestion d'erreur...
}
if ( succeeded ) {
try {
secondOperation() ;
} catch ( ... ) {
// gestion d'erreur (différente du précédante) ...
}
}
ou :
while ( ! end && getData() ) {
// traitement des données ...
}
if ( ! end ) {
// traitement de l'erreur ...
}
contre :
while ( ! end ) {
try {
getData() ;
// traitement des données...
} catch( ... ) {
// traitement de l'erreur...
end = true ;
}
}
Il faut aussi savoir que la gestion de code de retour est
coûteuse en temps de calcul tout le temps alors que la gestion
des exceptions ne coûte que quand une exception est levée ...
Tu as des mesures ? Jusqu'ici, je n'ai jamais rencontré une
application où le choix aurait fait une différence mesurable.
[...]
-- Assert (avec avortement en cas d'erreur) : tout ce qui
relève de l'erreur logiciel (y compris dans le code client)
ou d'une panne matérielle qui empêcherait au programme de
fonctionner.
Je suis d'accord pour l'erreur logiciel mais pas pour l'erreur
matérielle ! Lors d'une erreur matériel, une exception est
appropriée, quitte à simplement avertir l'utilisateur et
quitter ...
Tout dépend de l'erreur (et de l'application). Si l'erreur
matérielle est sur une périphérique sécondaire, tu as
probablement raison. Si c'est une faute de parité mémoire, en
revanche, ton programme est corrompu, et tout ce que tu fais de
plus pourrait faire encore plus de dégats. (Mais j'aurais dû
être plus précis.
Le problème de la gestion d'une panne matérielle
avec un assert et qu'il n'y aura plus aucune erreur de gérées
dès que le code est mis en production (i.e. donc plus en
debug).
Depuis quand ? Je n'ai jamais livré du code sans les assert ?
On *peut* supprimer les assert en production, si le profiler
montre qu'il pose un véritable problème de temps, mais c'est une
pratique exceptionnelle.
--
James Kanze GABI Software
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
Pierre Barbier de Reuille wrote:kanze wrote:Pierre Barbier de Reuille wrote:Michael wrote:Pierre Barbier de Reuille wrote
in news:44e2590c $0$18265$:
[...]
Je dois dire que je ne suis pas d'accord (même si, comme tu le
dis plus loin, c'est une affaire de jugement). Mais je ne
pense pas qu'il soit bon de mélanger code de retour et
exceptions.
Certainement pas. Certains types d'erreur sont rapportés par
des exceptions, d'autres par des codes de retour. Il n'y a pas
de mélange. (Il y a aussi, bien que moins souvent, des cas
particuliers où d'autres solutions s'imposent. Genre iostream,
avec un état dans l'objet.)Un des objectif des exceptions est de ne gérer les erreurs
qu'à l'endroit où tu peux y faire quelque chose ...
L'objectif des exceptions, c'est de pouvoir propager une erreur
assez loin sans que les niveaux intermédiaires aient à s'en
occuper (directement, en tout cas), avec un nettoyage approprié.
Si de par sa nature, l'erreur ne doit pas propager loin,
l'intérêt des exceptions se trouve grandement réduit. On en paie
le coût (en lourdeur, par exemple) pour rien.
En règle générale, on ne se sert d'une exception que quand on
est certain que l'erreur ne peut pas été traitée localement.
Dans la doute, on préfère le code de retour, parce que c'est
bien plus simple et plus léger, et c'est simple aussi pour le
client de le convertir en exception s'il ne peut pas le traiter
localement.
alors qu'avec les codes de retour, il faut propager les
erreurs à la main jusqu'à l'endroit qui peut gérer les
erreurs.
Justement. Il y a beaucoup d'erreurs qu'on traite localement, et
qu'on ne propage pas. Rien n'est gratuit, et c'est bien plus
pénible d'essayer à attrapper des exceptions que de simplement
tester un code de retour.Et, typiquement, les erreurs de saisies nécessiteront une
re-saisie des données et l'erreur sera donc gérées au mieux au
niveau de l'interface graphique (pour pouvoir redemander la
saisie).
Exactement. Typiquement, la saisie se trouve dans une boucle,
avec rupture de boucle quand elle est bonne ou quand
l'utilisateur démande un abort. Et c'est bien plus simple de
gérer une boucle avec un code de retour qu'avec une exception.
Si tu utilises un code de retour pour ça, ce sera très lourd à
gérer.
Je dirais le contraire. Si tu utilises une exception pour ça, ce
sera très lourd à gérer. (En Java, je n'avais pas toujours le
choix, et je t'assure, les exceptions, dans des cas comme
l'échec d'une ouverture de fichier, ça alourdit le code
énormement.)Pour moi, le seul cas où un code de retour est utilisable dans
un système utilisant les exceptions, est s'il est acceptable
d'avoir une erreur "silencieuse".
Sauf que si tu ne testes pas le code de retour, il y a une
avorte, non ? Au moins, les codes de retour que j'ai utilisé
ont en général un drappeau « lu », et un assert qu'il soit
vrai dans le destructeur.Par exemple, dans le cas d'un overflow numérique, le fait
d'avoir NaN ou inf à la place d'un nombre valide peut se
justifier, car il est probable que le client remplace ces
valeurs par autre chose par la suite ... mais c'est un des
rares exemples que je vois comme ça.
C'est un cas particulier où il est permis de différer le
traitement d'erreur. Les erreurs des iostream sont pareilles.
Dans les deux cas, il se trouve que l'objet (le virgule flottant
ou le flux) peut devenir invalid à la suite de prèsque toute
opération. Tester après chaque opération serait assez
fastidieux, et on a choisi l'option de propager l'invalidité à
travers toutes les opérations suivantes, pour pouvoir ne le
tester qu'une fois, à la fin.
Comme tu dis, de tels cas ne sont pas fréquents.De plus, je ne vois vraiment pas en quoi le code de retour
permet, plus que les exceptions, de récupérer l'exécution (ni
plus ni moins d'ailleurs ...).
C'est beaucoup moins lourd :
if ( ! premierOperation() ) {
// gestion d'erreur...
} else if ( ! secondOperation() ) {
// gestion d'erreur (différente du précédante) ...
}
contre :
bool succeeded = false ;
try {
premierOperation() ;
succeeded = true ;
} catch ( ... ) {
// gestion d'erreur...
}
if ( succeeded ) {
try {
secondOperation() ;
} catch ( ... ) {
// gestion d'erreur (différente du précédante) ...
}
}
ou :
while ( ! end && getData() ) {
// traitement des données ...
}
if ( ! end ) {
// traitement de l'erreur ...
}
contre :
while ( ! end ) {
try {
getData() ;
// traitement des données...
} catch( ... ) {
// traitement de l'erreur...
end = true ;
}
}Il faut aussi savoir que la gestion de code de retour est
coûteuse en temps de calcul tout le temps alors que la gestion
des exceptions ne coûte que quand une exception est levée ...
Tu as des mesures ? Jusqu'ici, je n'ai jamais rencontré une
application où le choix aurait fait une différence mesurable.
[...]-- Assert (avec avortement en cas d'erreur) : tout ce qui
relève de l'erreur logiciel (y compris dans le code client)
ou d'une panne matérielle qui empêcherait au programme de
fonctionner.
Je suis d'accord pour l'erreur logiciel mais pas pour l'erreur
matérielle ! Lors d'une erreur matériel, une exception est
appropriée, quitte à simplement avertir l'utilisateur et
quitter ...
Tout dépend de l'erreur (et de l'application). Si l'erreur
matérielle est sur une périphérique sécondaire, tu as
probablement raison. Si c'est une faute de parité mémoire, en
revanche, ton programme est corrompu, et tout ce que tu fais de
plus pourrait faire encore plus de dégats. (Mais j'aurais dû
être plus précis.
Le problème de la gestion d'une panne matérielle
avec un assert et qu'il n'y aura plus aucune erreur de gérées
dès que le code est mis en production (i.e. donc plus en
debug).
Depuis quand ? Je n'ai jamais livré du code sans les assert ?
On *peut* supprimer les assert en production, si le profiler
montre qu'il pose un véritable problème de temps, mais c'est une
pratique exceptionnelle.
--
James Kanze GABI Software
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
On 16 Aug 2006 06:14:33 -0700, "kanze" :-- Exception : ce sont les erreurs auxquelles on ne s'attend
pas dans un fonctionnement normal, mais qui peuvent se
produire dans des cas limites,
Je serais moins manichéen que toi sur la distinction code de
retour / exception. J'ai plutôt tendance à choisir suivant la
lisibilité de code plutôt que d'après une règle fixe.
En particulier, si réparer le problème (par exemple, demander
à l'utilisateur de saisir une nouvelle valeur) demande de
remonter plusieurs appels de fonctions, les exceptions sont
parfaitement adaptées.
Si, en plein milieu d'un calcul, on s'aperçoit que la valeur
saisie entraîne des résultats incorrects, j'aime pouvoir dire
"On arrête tout et on recommence avec une autre valeur", sans
avoir à mettre des dizaines de vérifications de valeurs de
retour qui alourdissent le code.
On 16 Aug 2006 06:14:33 -0700, "kanze" <kanze@gabi-soft.fr>:
-- Exception : ce sont les erreurs auxquelles on ne s'attend
pas dans un fonctionnement normal, mais qui peuvent se
produire dans des cas limites,
Je serais moins manichéen que toi sur la distinction code de
retour / exception. J'ai plutôt tendance à choisir suivant la
lisibilité de code plutôt que d'après une règle fixe.
En particulier, si réparer le problème (par exemple, demander
à l'utilisateur de saisir une nouvelle valeur) demande de
remonter plusieurs appels de fonctions, les exceptions sont
parfaitement adaptées.
Si, en plein milieu d'un calcul, on s'aperçoit que la valeur
saisie entraîne des résultats incorrects, j'aime pouvoir dire
"On arrête tout et on recommence avec une autre valeur", sans
avoir à mettre des dizaines de vérifications de valeurs de
retour qui alourdissent le code.
On 16 Aug 2006 06:14:33 -0700, "kanze" :-- Exception : ce sont les erreurs auxquelles on ne s'attend
pas dans un fonctionnement normal, mais qui peuvent se
produire dans des cas limites,
Je serais moins manichéen que toi sur la distinction code de
retour / exception. J'ai plutôt tendance à choisir suivant la
lisibilité de code plutôt que d'après une règle fixe.
En particulier, si réparer le problème (par exemple, demander
à l'utilisateur de saisir une nouvelle valeur) demande de
remonter plusieurs appels de fonctions, les exceptions sont
parfaitement adaptées.
Si, en plein milieu d'un calcul, on s'aperçoit que la valeur
saisie entraîne des résultats incorrects, j'aime pouvoir dire
"On arrête tout et on recommence avec une autre valeur", sans
avoir à mettre des dizaines de vérifications de valeurs de
retour qui alourdissent le code.
Alain Gaillard wrote:
[...]
... donc comment libérer l'objet alors que je n'ai pas un pointeur
dessus ?
Je pense que nous ne nous comprenons pas, j'ai écris:
objet* = new.....
// du code qui lève une exception
et j'avais bien dit que le bout de code était dans un contructeur. Sans
doute le terme objet a-t-il prêté à confusion. Il s'agit d'un pointeur
sur un objet membre d'une classe donc nous discutino à propos du
constructeur
quoiqu'il en soit, le code qui lève l'exception est *derrière* la
création de objet.
J'ai un pointeur dessus, je peux le libérer.
Si c'est objet* = new..... qui lève lui même l'exception, alors
effectivement il n'y a pas de pointeur dessus et rien à libérer.tableau "automatique" (i.e. alloué dans la pile) que seul l'objet levant
l'exception est détruit.
Je pense vraiment que nous ne parlons pas de la même chose.
Alain Gaillard wrote:
[...]
... donc comment libérer l'objet alors que je n'ai pas un pointeur
dessus ?
Je pense que nous ne nous comprenons pas, j'ai écris:
objet* = new.....
// du code qui lève une exception
et j'avais bien dit que le bout de code était dans un contructeur. Sans
doute le terme objet a-t-il prêté à confusion. Il s'agit d'un pointeur
sur un objet membre d'une classe donc nous discutino à propos du
constructeur
quoiqu'il en soit, le code qui lève l'exception est *derrière* la
création de objet.
J'ai un pointeur dessus, je peux le libérer.
Si c'est objet* = new..... qui lève lui même l'exception, alors
effectivement il n'y a pas de pointeur dessus et rien à libérer.
tableau "automatique" (i.e. alloué dans la pile) que seul l'objet levant
l'exception est détruit.
Je pense vraiment que nous ne parlons pas de la même chose.
Alain Gaillard wrote:
[...]
... donc comment libérer l'objet alors que je n'ai pas un pointeur
dessus ?
Je pense que nous ne nous comprenons pas, j'ai écris:
objet* = new.....
// du code qui lève une exception
et j'avais bien dit que le bout de code était dans un contructeur. Sans
doute le terme objet a-t-il prêté à confusion. Il s'agit d'un pointeur
sur un objet membre d'une classe donc nous discutino à propos du
constructeur
quoiqu'il en soit, le code qui lève l'exception est *derrière* la
création de objet.
J'ai un pointeur dessus, je peux le libérer.
Si c'est objet* = new..... qui lève lui même l'exception, alors
effectivement il n'y a pas de pointeur dessus et rien à libérer.tableau "automatique" (i.e. alloué dans la pile) que seul l'objet levant
l'exception est détruit.
Je pense vraiment que nous ne parlons pas de la même chose.
Fabien LE LEZ writes:
| On 16 Aug 2006 06:14:33 -0700, "kanze" :
| > -- Exception : ce sont les erreurs auxquelles on ne s'attend
| > pas dans un fonctionnement normal, mais qui peuvent se
| > produire dans des cas limites,
| Je serais moins manichéen que toi sur la distinction
| code de retour / exception. J'ai plutôt tendance à choisir suivant la
| lisibilité de code plutôt que d'après une règle fixe.
la correction d'un programme est-elle un corollaire de « lisibilité » ?
Fabien LE LEZ <gramster@gramster.com> writes:
| On 16 Aug 2006 06:14:33 -0700, "kanze" <kanze@gabi-soft.fr>:
| > -- Exception : ce sont les erreurs auxquelles on ne s'attend
| > pas dans un fonctionnement normal, mais qui peuvent se
| > produire dans des cas limites,
| Je serais moins manichéen que toi sur la distinction
| code de retour / exception. J'ai plutôt tendance à choisir suivant la
| lisibilité de code plutôt que d'après une règle fixe.
la correction d'un programme est-elle un corollaire de « lisibilité » ?
Fabien LE LEZ writes:
| On 16 Aug 2006 06:14:33 -0700, "kanze" :
| > -- Exception : ce sont les erreurs auxquelles on ne s'attend
| > pas dans un fonctionnement normal, mais qui peuvent se
| > produire dans des cas limites,
| Je serais moins manichéen que toi sur la distinction
| code de retour / exception. J'ai plutôt tendance à choisir suivant la
| lisibilité de code plutôt que d'après une règle fixe.
la correction d'un programme est-elle un corollaire de « lisibilité » ?
- si on oublie de vérifier un code de retour, une erreur
d'exécution peut passer inaperçue ;
- si on oublie d'intercepter une exception, une erreur
d'exécution peut arrêter le programme.
En pratique, la simplicité du code peut permettre de choisir la
méthode d'indication d'une erreur :
- si tu écris plusieurs try...catch dans la même fonction, il y
a des chances pour qu'une indication d'erreur par code de retour soit
plus adaptée ;
- si tu écris plusieurs constructions du style
if (f() != OK) return ERREUR;
il y a des chances pour qu'une exception soit plus adaptée.
- si on oublie de vérifier un code de retour, une erreur
d'exécution peut passer inaperçue ;
- si on oublie d'intercepter une exception, une erreur
d'exécution peut arrêter le programme.
En pratique, la simplicité du code peut permettre de choisir la
méthode d'indication d'une erreur :
- si tu écris plusieurs try...catch dans la même fonction, il y
a des chances pour qu'une indication d'erreur par code de retour soit
plus adaptée ;
- si tu écris plusieurs constructions du style
if (f() != OK) return ERREUR;
il y a des chances pour qu'une exception soit plus adaptée.
- si on oublie de vérifier un code de retour, une erreur
d'exécution peut passer inaperçue ;
- si on oublie d'intercepter une exception, une erreur
d'exécution peut arrêter le programme.
En pratique, la simplicité du code peut permettre de choisir la
méthode d'indication d'une erreur :
- si tu écris plusieurs try...catch dans la même fonction, il y
a des chances pour qu'une indication d'erreur par code de retour soit
plus adaptée ;
- si tu écris plusieurs constructions du style
if (f() != OK) return ERREUR;
il y a des chances pour qu'une exception soit plus adaptée.
Fabien LE LEZ wrote:- si on oublie de vérifier un code de retour, une erreur
d'exécution peut passer inaperçue ;
Si on oublie de vérifier un code de retour, on a l'échec d'une
assertion. Ça ne se passe pas inaperçu chez moi.- si on oublie d'intercepter une exception, une erreur
d'exécution peut arrêter le programme.
Si les tests ne provoque pas l'erreur, et qu'on a oublie
d'intercepter l'exception, ça passe inaperçu. Avec le code de
retour, en revanche, il y a échec de l'assertion si on ne
vérifie pas, même dans le cas où il n'y a pas d'erreur.En pratique, la simplicité du code peut permettre de choisir la
méthode d'indication d'une erreur :
- si tu écris plusieurs try...catch dans la même fonction, il y
a des chances pour qu'une indication d'erreur par code de retour soit
plus adaptée ;
Même si chaque appel de la fonction qui lève l'exception doit
être encadré dans un try/catch.- si tu écris plusieurs constructions du style
if (f() != OK) return ERREUR;
il y a des chances pour qu'une exception soit plus adaptée.
Tout à fait.
La seule chose à ajouter, c'est que si tu écris une
bibliothèque, et tu ne sais pas trop où le client va traiter
l'erreur, il vaut mieux pécher du côté du code de retour.
Simplement parce que c'est beaucoup plus facile au client
d'écrire :
if ( f() != OK ) throw whatever ;
que d'écrire :
try {
f() ;
} catch ( Whatever& error ) {
// ...
}
--
James Kanze GABI Software
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
Fabien LE LEZ wrote:
- si on oublie de vérifier un code de retour, une erreur
d'exécution peut passer inaperçue ;
Si on oublie de vérifier un code de retour, on a l'échec d'une
assertion. Ça ne se passe pas inaperçu chez moi.
- si on oublie d'intercepter une exception, une erreur
d'exécution peut arrêter le programme.
Si les tests ne provoque pas l'erreur, et qu'on a oublie
d'intercepter l'exception, ça passe inaperçu. Avec le code de
retour, en revanche, il y a échec de l'assertion si on ne
vérifie pas, même dans le cas où il n'y a pas d'erreur.
En pratique, la simplicité du code peut permettre de choisir la
méthode d'indication d'une erreur :
- si tu écris plusieurs try...catch dans la même fonction, il y
a des chances pour qu'une indication d'erreur par code de retour soit
plus adaptée ;
Même si chaque appel de la fonction qui lève l'exception doit
être encadré dans un try/catch.
- si tu écris plusieurs constructions du style
if (f() != OK) return ERREUR;
il y a des chances pour qu'une exception soit plus adaptée.
Tout à fait.
La seule chose à ajouter, c'est que si tu écris une
bibliothèque, et tu ne sais pas trop où le client va traiter
l'erreur, il vaut mieux pécher du côté du code de retour.
Simplement parce que c'est beaucoup plus facile au client
d'écrire :
if ( f() != OK ) throw whatever ;
que d'écrire :
try {
f() ;
} catch ( Whatever& error ) {
// ...
}
--
James Kanze GABI Software
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
Fabien LE LEZ wrote:- si on oublie de vérifier un code de retour, une erreur
d'exécution peut passer inaperçue ;
Si on oublie de vérifier un code de retour, on a l'échec d'une
assertion. Ça ne se passe pas inaperçu chez moi.- si on oublie d'intercepter une exception, une erreur
d'exécution peut arrêter le programme.
Si les tests ne provoque pas l'erreur, et qu'on a oublie
d'intercepter l'exception, ça passe inaperçu. Avec le code de
retour, en revanche, il y a échec de l'assertion si on ne
vérifie pas, même dans le cas où il n'y a pas d'erreur.En pratique, la simplicité du code peut permettre de choisir la
méthode d'indication d'une erreur :
- si tu écris plusieurs try...catch dans la même fonction, il y
a des chances pour qu'une indication d'erreur par code de retour soit
plus adaptée ;
Même si chaque appel de la fonction qui lève l'exception doit
être encadré dans un try/catch.- si tu écris plusieurs constructions du style
if (f() != OK) return ERREUR;
il y a des chances pour qu'une exception soit plus adaptée.
Tout à fait.
La seule chose à ajouter, c'est que si tu écris une
bibliothèque, et tu ne sais pas trop où le client va traiter
l'erreur, il vaut mieux pécher du côté du code de retour.
Simplement parce que c'est beaucoup plus facile au client
d'écrire :
if ( f() != OK ) throw whatever ;
que d'écrire :
try {
f() ;
} catch ( Whatever& error ) {
// ...
}
--
James Kanze GABI Software
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