OVH Cloud OVH Cloud

Erreur contre exception : la solution de normand

13 réponses
Avatar
Jean-Marc Desperrier
Salut,

En regardant quelques discussion intéressante sur la meilleure façon de
gérer les erreurs dans un programme C++ ici :
http://damienkatz.net/2006/04/error_code_vs_e.html

Je suis tombé sur la proposition suivante pour ne pas avoir à choisir au
moment de l'écriture d'une fonction si elle sera gérée avce un code
d'erreur ou une exception :
"There is a way to let the caller make the decision of code vs.
exception. I wrote a class that can be returned just like an error code,
but it automatically throws an exception if its status isn't checked. It
allowed us to convert an entire library written with an error code
paradigm, to optionally use exceptions instead."

En fait c'est hyper simple à faire, je me demande ce que vous pensez de
cette technique ?

Illustration à la suite :
include <iostream>

class ErrorValue {
int rv;
bool checked;

public:
ErrorValue(int v): rv(v), checked(false) {}
~ErrorValue() {if (checked==false) throw *this;}
operator int() {checked=true;return rv;}
};

ErrorValue test1 () {
return ErrorValue(1);
}

ErrorValue test2 () {
return ErrorValue(2);
}

int main(int argc, char * argv[]) {
try {
int rc = test1();
std::cout << "Return ErrorValue of first test is "
<< rc << "\n";
test2();
} catch (ErrorValue &ev) {
std::cout << "ErrorValue catched is "
<< ev << "\n" ;
}
}

PS : Avec cette écriture, il y a quand même un petit piège potentiel,
qu'on retrouve dans ma première version avec "catch (ErrorValue ev)" où
le programme ne se terminait pas très bien, pe que "throw rv" serait
plus raisonnable ou bien contruire un type qui n'est pas vérifié pour ce
throw.

3 réponses

1 2
Avatar
Jean-Marc Desperrier
Laurent Deniau wrote:
hormis le fait que cette classe suppose que le code de retour est un
entier, qu'il empeche de cascader les appels


James fait la même remarque sur les cascade, je veux bien regarder s'il
y a une manière propre de corriger cela.

et qu'il est facile de lui
faire lever une exception pendant un stack unwinding ou encore de perdre
accidentellement la trace de la derniere erreur. Je pense qu'il serait
aussi utile d'ajouter qqchose comme:

~ErrorValue() {if (checked=úlse && rv!=not_an_error) throw *this;}

Parce que sinon toutes les valeurs de retour se transforment en
exception (pas tres utile). Il n'a pas du etre beaucoup utilise ce code,
non?


Le code je l'ai écrit en 5 minutes après lu le commentaire sur le bug
pour transformer cette idée en une référence concrête sur comment le
faire, en vérifiant juste qu'il compilait sans crash.

C'était une bonne chose d'ailleurs : Sans exemple concret et facile à
critiquer, ça aurait 10 fois moins attiré l'attention et les remarques :-)

Avatar
espie
In article ,
James Kanze wrote:
Marc Boyer wrote:

[...]
Après, faudrait aussi vérfier dans la norme la possibilité de lancer
une exception dans un destructeur.


C'est permis pas le langage, et je n'ai jamais entendu parler
d'un compilateur où ça fait un problème. Il y a des
restrictions : si le destructeur d'un objet dans une collection
standard lève une exception, c'est un comportement indéfini, par
exemple, et je ne suis pas sûr que le comportement soit
réelement bien défini si le destructeur est appelé dans une
expression de delete, mais ces restrictions ne doivent pas poser
de problème pour l'utilisation qu'il a en vue.


Il me semble bien que ca fait partie des regles enoncees par Herb Sutter
dans _exceptional C++_: ne jamais lever d'exception dans les destructeurs.

Cote control-flow, on n'a pas reellement suffisamment de marge de manoeuvre
pour pouvoir se le permettre.

De facon tres generale, si on part de l'idee que les exceptions vont
permettre de `decentraliser' la gestion d'erreur, ca se justifie tout a
fait: on a tres peu de garantie sur les circonstances dans lesquelles un
destructeur est appele, et les chances de passer par celui-ci lors de la
levee d'une autre exception sont franchement importantes...

Bref, tout mecanisme du type `exception dans le destructeur' va limiter
a un style de codage bien precis, ou le programmeur DOIT savoir precisement
comment marche ce retour d'erreur... necessite de traiter ces `objets
d'exception' sous forme de reference en permanence (ou un auto_ptr ?), bonne
chance avec la gestion des temporaires.

vu les erreurs qu'on peut faire sur 3 lignes de code dans lesquelles on
demande `combien peut-il y avoir d'appels au constructeur de copie ?'
je ne suis pas franchement optimiste quant a la possibilite d'ecrire du
code correct avec ce type de mecanisme tordu...

Comme tu le dis, ce genre de trucs est par exemple interdit pour les types
utilisables dans les template de la bibliotheque standard. C'est pas trop
etonnant, ca veut juste dire que personne ne sait ecrire du code template
robuste en presence de types dont les destructeurs peuvent lever des
exceptions...


Avatar
James Kanze
Marc Espie wrote:
In article ,
James Kanze wrote:
Marc Boyer wrote:

[...]
Après, faudrait aussi vérfier dans la norme la possibilité de lancer
une exception dans un destructeur.


C'est permis pas le langage, et je n'ai jamais entendu parler
d'un compilateur où ça fait un problème. Il y a des
restrictions : si le destructeur d'un objet dans une collection
standard lève une exception, c'est un comportement indéfini, par
exemple, et je ne suis pas sûr que le comportement soit
réelement bien défini si le destructeur est appelé dans une
expression de delete, mais ces restrictions ne doivent pas poser
de problème pour l'utilisation qu'il a en vue.


Il me semble bien que ca fait partie des regles enoncees par Herb Sutter
dans _exceptional C++_: ne jamais lever d'exception dans les destructeurs.


Je ne sais pas. C'est certainement déconseillé, sauf cas
d'exception.

Mais attention aussi : _Exceptional C++_ n'est pas la norme (et
ne prétend pas l'être) ; c'est une collection des conseils, des
règles de programmation, si tu veux. (Encore que... le livre en
fait traite un certain nombre de questions, et toutes les
questions n'en ont pas une règle de programmation comme
réponse.) Aussi, dans le livre, les règles sont motivées, et
elles permettent toujours des exceptions.

Cote control-flow, on n'a pas reellement suffisamment de marge de manoeuv re
pour pouvoir se le permettre.


Ça dépend de la contexte. Il y a aussi une certaine catégorie de
classes qui n'existe que pour lever une exception dans le
destructeur. C'est une façon de différer la levée jusqu'à avoir
fini un traitement intermédiaire, par exemple.

De facon tres generale, si on part de l'idee que les exceptions vont
permettre de `decentraliser' la gestion d'erreur, ca se justifie tout a
fait: on a tres peu de garantie sur les circonstances dans lesquelles un
destructeur est appele, et les chances de passer par celui-ci lors de la
levee d'une autre exception sont franchement importantes...

Bref, tout mecanisme du type `exception dans le destructeur' va limiter
a un style de codage bien precis,


Tout modèle de conception, tout idiome, s'applique à un style de
codage précis. Si pour une raison quelconque, tu veux différer
la levée de l'exception jusqu'à la fin de l'expression, par
exemple, à la place de throw, tu crées un temporaire qui fait le
throw dans son destructeur. (C'est utile, par exemple, si tu
veux loguer la contexte d'où l'exception a été levée.)

Comme tu le dis, ce genre de trucs est par exemple interdit pour les types
utilisables dans les template de la bibliotheque standard.


Une classe sans opérateur d'affectation accessible est aussi
interdit dans les collections de la bibliotheque standard. Ça ne
veut pas dire qu'il faut pourvoir toutes les classes avec des
opérateurs d'affectation accessibles. Ça veut dire simplement
que les collections standard stockent de valeurs, et qu'elles ne
peuvent donc pas servir pour des classes qui n'ont pas
sémantique de valeur. Une classe qui lève une exception dans son
destructeur n'est prèsque sûrement pas de valeur ; il n'y a
donc pas de contradiction ici.

C'est pas trop
etonnant, ca veut juste dire que personne ne sait ecrire du code template
robuste en presence de types dont les destructeurs peuvent lever des
exceptions...


Ni en présence des types qui ne supportent ni la copie ni
l'affectation. De même, on ne sait pas écrire une fonction de
sort sur un type qui ne supporte pas de relation d'ordre. Ça me
semble un peu normal. Mais ça ne veut pas dire qu'il faut qu'on
puisse mettre tous les types dans une collection, ni que tous
les types doivent pouvoir être triés.

--
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



1 2