OVH Cloud OVH Cloud

De l'usage des exceptions

106 réponses
Avatar
Guillaume Gourdin
Bonjour à tous,

selon ma (modeste) expérience, les exceptions en C++sont très largement sous
utilisées, et on en reste (me semble t-il) toujours à la bonne vieille
méthode du retour d'un code d'erreur. Je suis un peu surpris de cet état de
fait, les exceptions étant supposées apporter une gestion plus performantes
des erreurs. J'ai dons quelques questions: est-il vrai que les exceptions
sont sous utilisées? Quels sont vos retours d'expérience? Ou bien y a t'il
une raison plus profonde à la sous-utilisation des exceptions?

Merci!

- Guillaume -

10 réponses

Avatar
ld
On 7 juil, 18:08, Jean-Marc Bourguet wrote:
> Il ne modifie pas le parseur, ce sont des combinators qui demande au
> parseur de valider un input en utilisant des combinator de plus bas
> niveau (char ou wchar_t). Et c'est la raison pour laquelle ils ne sont
> pas membre. C'est le parseur qui track ou il en est. Libre ensuite de
> convertir la validation courante en objet (type).

As-tu une reference?



le plus simple que je connaisse (en C) c'est
http://www.cs.chalmers.se/~koen/ParserComboC/parser-combo-c.html
qui montre comment l'utiliser mais pas ce que le parser doit faire
pour gerer les contextes et le backtracking (je devrais ajouter
"efficacement").

Sinon Spirit dans Boost est base sur les memes concepts je crois mais
mes essais il y a 5-6 ans donnaient des temps de compilation
prohibitif pour des grammaires non trivial. Je suppose qu'aujourd'hui
le probleme est regle.

Ce qui m'a le plus inspire et je pense pas etre le seul, c'est la lib
Parsec pour Haskell.
http://book.realworldhaskell.org/read/using-parsec.html
http://www.haskell.org/haskellwiki/Parsec

 Le nom m'evoque une technique qui est proche, mais
visiblement il y a des details qui different au point que nous ne nous
comprennons pas du tout.  En particulier, tu as l'air d'impliquer qu'on
peut avoir des messages d'erreur raisonnables pour une description qui ne
tient pas compte de ca,



les messages d'erreurs sont un des elements du contexte gerer par le
parseur, mais initie par les combinators. Le parseur ne connait pas la
semantique de la grammaire (et donc pas la signification des echecs),
seulement sa description sous forme de combinateurs dont il fournit
les plus elementaires. En fait, meme si cette methode ne creer pas des
parseurs optimaux, elle a le merite d'etre tres simple.

alors qu'utilisee de maniere trop naive, la
technique que je connais donne facilement des messages d'erreur
inutilisables, pires que le "syntax error" de yacc car ils ne pointent me me
pas a la fin du prefixe viable le plus long (on peut relativement
facilement arriver a cela, mais c'est quand meme le degre 0 du message
d'erreur).



le probleme de yacc, c'est que les reductions ne tiennent pas compte
des contextes (on peut bidouiller qqchose). Il est donc difficile de
savoir a partir de ou ca commencer a derailler.

a+, ld.
Avatar
Patrick 'Zener' Brunet
Bonsoir.

"Michael Doubez" a écrit dans le message
de news
On 5 juil, 11:41, "Patrick 'Zener' Brunet"
wrote:
"Guillaume Gourdin" a écrit dans le message
de news 4a4f3042$0$30992$

selon ma (modeste) expérience, les exceptions en C++sont très
largement sous utilisées, et on en reste (me semble t-il) toujours
à la bonne vieille méthode du retour d'un code d'erreur. Je suis
un peu surpris de cet état de fait, les exceptions étant supposées
apporter une gestion plus performantes des erreurs. J'ai dons
quelques questions: est-il vrai que les exceptions sont sous
utilisées? Quels sont vos retours d'expérience? Ou bien y a t'il
une raison plus profonde à la sous-utilisation des exceptions?



En complément aux arguments de Fabien Le Lez, en lisant du code C++
utilisant des exceptions, on voit souvent des choses aussi lourdes
que risquées, qui peuvent donc "discréditer" les exceptions aux
yeux de certains.

Lancer une exception implique de la construire, donc il vaut mieux
que cette partie-là soit garantie.



C'est pour ça qu'il y a la garantie nothrow. Mais dans le cas
général, à moins d'un état grave de manque de mémoire, la
construction des exceptions n'est pas un problème.




Effectivement, tirer une exceptions durant le traitement d'une autre, pour
moi c'est du mauvais goût.

Mais avant ça, il y a l'échec de la construction de l'exception elle-même:
- par manque de mémoire si elle n'est pas pré-allouée,
- par échec d'une fonction de renseignement quelconque si on veut remplir un
diagnostic verbeux.


Il n'est pas rare de voir des exceptions créées par un new, et
pour faire bonne mesure, on insère tout un tas de diagnostics
dedans sous forme de strings...



C'est du code à la Java.



Que nenni ! C'est si tentant en C++ de construire le message d'erreur eu
niveau du cas d'échec et de le faire transporter par l'exception.
Qui va donc contenir quelques strings et tout un fatras d'opérateurs << ou
concaténation (avec donc dans la string un maximum de réallocations de
buffers).

Tout ça pour traiter un incident tels qu'un échec d'allocation de
mémoire ? Il doit bien y avoir des gens que ça ennuie...



Je prefère une exception à un NULL à vérifier chaque fois.



On parle bien de la construction de l'exception elle-même, pas de l'objet
qui va la provoquer.

Dans ce cas, moi aussi je préfère que l'op new génère une exception. Mais
s'il doit faire un malloc() pour ça, ça craint un peu.

De toutes
façons, je ne sais pas quoi faire si j'ai plus de mémoire, il y a
peux de chance qu'essayer de nouveau marche. Le mieux est encore de
revenir à un état antérieur qui se prétend capable de gérer la
situation.



Ben idéalement on peut envisager de libérer des choses pour restituer de la
mémoire. Anticiper un cleanup par exemple, retardé pour des raisons de
performances...
Evidemment le scénario est discutable sur une station avec quelques Go de
RAM et un gros disque, mais sur un système embarqué un peu étroit et pour
lequel on fait du process complexe qui nécessite de l'allocation dynamique
(un peu d'IA par exemple), c'est la bonne manière d'envisager les choses.

A priori c'est-là la cause essentielle du divorce entre l'informatique
industrielle et celles "de gestion" et "avancée". Il serait temps de les
réconcillier... Je ne vois rien de malsain dans une gestion de la mémoire en
temps que ressource comptabilisée plutôt que supposée "prévue suffisante".

Ensuite, comment passe-t-on cette exception (que "throwe"-t-on) ?
Par adresse, référence ou par copie ?



Pour autant que je sache, on ne peux pas lancer par référence.
Un bonne règle est de lancer par valeur et de catcher par référence
pour éviter le slicing.




Oui, j'ai été confus... On lance par valeur et on propage/manipule par
référence ensuite.

Ben en fait je ne sais pas vraiment comment ça s'implémente à la
compilation: à quel moment se fait le démontage de la pile par rapport au
cycle de l'exception.
Parce qu'elle est instanciée en sommet de pile, et le catch se trouve bien
plus profond... Si dans le bloc catch je déclare une variable automatique,
elle se met où sur la pile ?
Est-ce que tous les compilos respectent une norme pour ça ?

Ce qui m'ennuie, c'est que si pour une raison quelconque le passage par
référence n'est que symbolique et qu'en fait techniquement il y a passage
par valeur, alors c'est pas grave si l'exception fait quelques octets, mais
dans le cas d'un gros truc comme évoqué au début, a priori c'est monstrueux.

C'est pouquoi je disais:

La copie est plus naturelle, mais elle implique effectivement de
copier tout ça (donc de mouliner intérieurement du new et du
delete).



Je ne comprends pas ce que tu veux dire ici.




Voilà.


Reste à voir ce qu'on en fait à la fin (le catch).

Si le système d'exceptions est bien fait, d'une part on a une
hiérarchie bien claire permettant de cascader les catch
spécifiques et de se passer du catch-all (sauf pour faire un
cleanup intermédiaire suivi d'un rethrow).



Si je catche quelque chose, c'est que j'ai prévu de le traiter et
que personne ne pense pouvoir le faire dans une sosu-couche donc je
sais bien ce que je doit catcher.



Ben si on écrit un code utilisant la fonction DoMachin() de la librairie
Toto v5.1, qui elle-même utilise la Tata v3.6 etc., on lit à fond la doc de
DoMachin(), et on fait confiance aux auteurs de Toto pour bien maîtriser ce
qui sort de Tata...
Le problème, c'est de savoir s'ils ont bien anticipé la Tata v4.0, et aussi
ce qu'ils vont changer eux-mêmes dans Toto 6.0.
Avec les DLL, ça peut changer par en-dessous sans que le code appelant doive
forcément être recompilé, ensuite on est supposé être à l'affut des news et
lire tous les changelogs...
C'est stressant. On peut avoir envie de mettre un catch-all, au cas où.

On disait plus bas:

Généralement on est bien embêté parce qu'on se tape pour
l'occasion une librairie plus ou moins exotique et on n'a pas trop
le temps de creuser son fonctionnement intime (c'est même ce qu'on
veut éviter - on a la pression bien sûr):
- Pour le détail des causes d'échec possibles, normalement, on
regarde les retours de la fonction appelée, et on fait un switch
avec un default... - Pour une exception, ça peut venir de plus
profond et donc c'est assez obtus. On ne veut pas en oublier une
parce que ça conduit à un plantage et c'est pas vendeur. Et ça
peut arriver par la suite avec une nouvelle version de ladite
librairie. D'où le choix du catch-all équivalent du défault...



Il y a plein de façon d'avoir une bibliothèque mal conçue en dehors
des exceptions.



Ah que oui ! Mais on a pas trop le choix, il faut vivre avec.
D'où la programmation défensive...

[...]
D'expérience, souvent on voit un catch-all avec dedans un
printf("oh zut, ça a foiré") suivi de return(ERROR).



Il y a beaucoup de cas où il est difficile de trouver une façon
gracieuse de gérer l'erreur. S'assurer que l'utilisateur n'a pas
perdu de données et lui donner une idée du diagnostique est un
minimum mais souvent c'est déjà pas mal.
[...]



Dans le contrôle de process, la règle c'est que ça ne doit pas foirer, et si
ça foire quand même, ça doit se débrouiller pour se récupérer tout seul et
au mieux.

Moi j'aime pas trop les logiciels qui disent "au
secours, j'y arrive pas", de plus je fais dans le process
automatisé, alors souvent il n'y a personne pour lire le message.



Un petit mail à l'administrateur, ça fait toujours plaisir :)




Ce genre de truc n'a pas d'email en général.

Exemple vécu (même pas besoin d'un missile, un trieur industriel):

La machine (1 hectare environ) est pilotée directement par des racks OS/9 ou
similaires, mais pour le contrôle de process on met volontiers du PC
(plusieurs rendondants éventuellement) parce que:
- c'est puissant pour pas cher,
- ça permet de faire des IHM évoluées que le client réclame,
- ça peut intégrer une DB Oracle ou Sybase,
- c'est moins impossible à interfacer avec du gros système COBOL (côté
compta),
- les développeurs qui connaissent se trouvent plus facilement sur le
marché,
- divers...

En fonctionnement normal, s'il y a un écran, il est dans une armoire et
personne ne le regarde, hors maintenance.
Quand bien même quelqu'un verrait la MessageBox "Erreur d'accès au fichier
TOTO.DAT", la plupart des opérateurs ne savent même pas ce qu'est un
fichier.
Et si le responsable de prod intervient, il va bien comprendre "qu'il y a
une .erde avec l'informatique", mais ensuite ?
Usine bloquée à 2h00 du mat, 500 opérateurs qui poireautent, les camionneurs
aussi, le réassort de toute la distribution de la région pour le lendemain
première heure qui est compromis...
Là le responsable réveille son PDG qui réveille celui de la SSII, et vous
imaginez la suite...

Donc à moins d'un tremblement de terre majeur, le process DOIT se
débrouiller tout seul :o)

Evidemment c'est très dûr à réussir, mais c'est quand même bien de ne pas y
renoncer d'emblée pour se faciliter le codage.
Alors on essaie de blinder au maximum et de récupérer à tout prix tout
problème qui peut l'être d'une manière quelconque.

--
Cordialement.

* Patrick BRUNET www.ipzb.fr www.ipzb-pro.com
* E-mail: lien sur http://zener131.eu/ContactMe
Avatar
Gabriel Dos Reis
ld writes:

[...]

| Ce qui m'a le plus inspire et je pense pas etre le seul, c'est la lib
| Parsec pour Haskell.
| http://book.realworldhaskell.org/read/using-parsec.html
| http://www.haskell.org/haskellwiki/Parsec

De manière assez intéressante, les combinateurs de parseurs ont été
« inventés » par Bill Burge. Il a documenté la techniqu e dans un bouquin
intéressant : chapitre 4 de « Recursive Programming Techniques », publié
en 1975.
Bill est aussi l'auteur originel du parser de « Boot » et du lang age
d'interprête du système AXIOM.

http://open-axiom.svn.sourceforge.net/viewvc/open-axiom/trunk/src/boot/p arser.boot?view=markup
http://open-axiom.svn.sourceforge.net/viewvc/open-axiom/trunk/src/interp /cparse.boot?view=markup


L'idée fondamentale est d'utiliser le langage d'implémentation po ur
exprimer directement les « méta règles ». Dans ce cas, effectivement la
description elle même ne gère pas directement les menu détai ls comme fin
de fichiers ou corruption de l'entrée : c'est le boulot des
« mini parseurs » de plus bas niveau.

J'ai aussi réecrit le parseur de Boot en C++ en utilisant les
combinateurs de parseurs : c'est assez simple à mettre en oeuvre avec
les templates et fonction objects. Et cela va vite !

-- Gaby
Avatar
Michael Doubez
On 8 juil, 00:11, ld wrote:
[snip]
Sinon Spirit dans Boost est base sur les memes concepts je crois mais
mes essais il y a 5-6 ans donnaient des temps de compilation
prohibitif pour des grammaires non trivial. Je suppose qu'aujourd'hui
le probleme est regle.



Je l'ai utilisé il y a un an sur une grammaire simple et le temps de
compilation était toujours trèèèès long.
Mais boost ne peut pas y faire grand chose.

--
Michael
Avatar
Michael Doubez
On 8 juil, 00:18, "Patrick 'Zener' Brunet"
wrote:

"Michael Doubez" a écrit dans le message
 de news om



> On 5 juil, 11:41, "Patrick 'Zener' Brunet"
> wrote:
>> "Guillaume Gourdin" a écrit dans le message
>> de news 4a4f3042$0$30992$

>>> selon ma (modeste) expérience, les exceptions en C++sont très
>>> largement sous utilisées, et on en reste (me semble t-il) toujours
>>> à la bonne vieille méthode du retour d'un code d'erreur. Je suis
>>> un peu surpris de cet état de fait, les exceptions étant suppos ées
>>> apporter une gestion plus performantes des erreurs. J'ai dons
>>> quelques questions: est-il vrai que les exceptions sont sous
>>> utilisées? Quels sont vos retours d'expérience? Ou bien y a t'il
>>> une raison plus profonde à la sous-utilisation des exceptions?

>> En complément aux arguments de Fabien Le Lez, en lisant du code C++
>> utilisant des exceptions, on voit souvent des choses aussi lourdes
>> que risquées, qui peuvent donc "discréditer" les exceptions aux
>> yeux de certains.

>> Lancer une exception implique de la construire, donc il vaut mieux
>> que cette partie-là soit garantie.

> C'est pour ça qu'il y a la garantie nothrow. Mais dans le cas
> général, à moins d'un état grave de manque de mémoire, la
> construction des exceptions n'est pas un problème.

Effectivement, tirer une exceptions durant le traitement d'une autre, pou r
moi c'est du mauvais goût.

Mais avant ça, il y a l'échec de la construction de l'exception elle- même:
- par manque de mémoire si elle n'est pas pré-allouée,
- par échec d'une fonction de renseignement quelconque si on veut rempl ir un
diagnostic verbeux.



C'est un scénario de situation vraiment critique. Quand on en arrive
là, les chances de s'en sortir sont minces.

>> Il n'est pas rare de voir des exceptions créées par un new, et
>> pour faire bonne mesure, on insère tout un tas de diagnostics
>> dedans sous forme de strings...

> C'est du code à la Java.

Que nenni ! C'est si tentant en C++ de construire le message d'erreur eu
niveau du cas d'échec et de le faire transporter par l'exception.
Qui va donc contenir quelques strings et tout un fatras d'opérateurs << ou
concaténation (avec donc dans la string un maximum de réallocations d e
buffers).



Dans ce cas, il y aura une exception lancée par la STL pour signaler
l'impossibilité d'allouer la mémoire, ce qui reviens plus ou moins au
même (minus les informations de debug).

>> Tout ça pour traiter un incident tels qu'un échec d'allocation de
>> mémoire ? Il doit bien y avoir des gens que ça ennuie...

> Je prefère une exception à un NULL à vérifier chaque fois.

On parle bien de la construction de l'exception elle-même, pas de l'obj et
qui va la provoquer.



Ok. J'avais pas compris.


Dans ce cas, moi aussi je préfère que l'op new génère une excepti on. Mais
s'il doit faire un malloc() pour ça, ça craint un peu.

> De toutes
> façons, je ne sais pas quoi faire si j'ai plus de mémoire, il y a
> peux de chance qu'essayer de nouveau marche. Le mieux est encore de
> revenir à un état antérieur qui se prétend capable de gérer l a
> situation.

Ben idéalement on peut envisager de libérer des choses pour restituer de la
mémoire. Anticiper un cleanup par exemple, retardé pour des raisons d e
performances...
Evidemment le scénario est discutable sur une station avec quelques Go de
RAM et un gros disque, mais sur un système embarqué un peu étroit e t pour
lequel on fait du process complexe qui nécessite de l'allocation dynami que
(un peu d'IA par exemple), c'est la bonne manière d'envisager les chose s.



Oui ou lancer un mode dégradé. OK

A priori c'est-là la cause essentielle du divorce entre l'informatique
industrielle et celles "de gestion" et "avancée". Il serait temps de le s
réconcillier... Je ne vois rien de malsain dans une gestion de la mém oire en
temps que ressource comptabilisée plutôt que supposée "prévue suf fisante".



C'est toujours une question de coûts. C'est moins cher d'acheter une
barette mémoire que de passer du temps sur du code.

>> Ensuite, comment passe-t-on cette exception (que "throwe"-t-on) ?
>> Par adresse, référence ou par copie ?

> Pour autant que je sache, on ne peux pas lancer par référence.
> Un bonne règle est de lancer par valeur et de catcher par référen ce
> pour éviter le slicing.

Oui, j'ai été confus... On lance par valeur et on propage/manipule pa r
référence ensuite.

Ben en fait je ne sais pas vraiment comment ça s'implémente à la
compilation: à quel moment se fait le démontage de la pile par rappor t au
cycle de l'exception.



La pile est dépilée jusqu'au handler le plus proche qui match
l'exception et le controle lui est transféré.

Parce qu'elle est instanciée en sommet de pile, et le catch se trouve b ien
plus profond... Si dans le bloc catch je déclare une variable automatiq ue,
elle se met où sur la pile ?



L'endroit de l'allocation est /unspecified/ d'après le standard
(§15.1/4)

Elle est traitée de la même façon qu'une valeur retournée par retur n.


Est-ce que tous les compilos respectent une norme pour ça ?

Ce qui m'ennuie, c'est que si pour une raison quelconque le passage par
référence n'est que symbolique et qu'en fait techniquement il y a pas sage
par valeur, alors c'est pas grave si l'exception fait quelques octets, ma is
dans le cas d'un gros truc comme évoqué au début, a priori c'est mo nstrueux.



Si c'est comme un return, le compilateur est donc en droit de faire
l'élision je suppose. Je ne sais pas si la sémantique de move change
quelque chose à ça dans C++0x.

A noter que l'utilisation de throw; pour relancer l'exception,
l'exception n'est pas détruite.
[snip]
>> Si le système d'exceptions est bien fait, d'une part on a une
>> hiérarchie bien claire permettant de cascader les catch
>> spécifiques et de se passer du catch-all (sauf pour faire un
>> cleanup intermédiaire suivi d'un rethrow).

> Si je catche quelque chose, c'est que j'ai prévu de le traiter et
> que personne ne pense pouvoir le faire dans une sosu-couche donc je
> sais bien ce que je doit catcher.

Ben si on écrit un code utilisant la fonction DoMachin() de la librairi e
Toto v5.1, qui elle-même utilise la Tata v3.6 etc., on lit à fond la doc de
DoMachin(), et on fait confiance aux auteurs de Toto pour bien maîtrise r ce
qui sort de Tata...
Le problème, c'est de savoir s'ils ont bien anticipé la Tata v4.0, et aussi
ce qu'ils vont changer eux-mêmes dans Toto 6.0.
Avec les DLL, ça peut changer par en-dessous sans que le code appelant doive
forcément être recompilé, ensuite on est supposé être à l'aff ut des news et
lire tous les changelogs...
C'est stressant. On peut avoir envie de mettre un catch-all, au cas où.



C'est une bonne pratique de na pas laisser les exception traverser les
bords d'une bibliothèque.


On disait plus bas:

>> Généralement on est bien embêté parce qu'on se tape pour
>> l'occasion une librairie plus ou moins exotique et on n'a pas trop
>> le temps de creuser son fonctionnement intime (c'est même ce qu'on
>> veut éviter - on a la pression bien sûr):
>> - Pour le détail des causes d'échec possibles, normalement, on
>> regarde les retours de la fonction appelée, et on fait un switch
>> avec un default... - Pour une exception, ça peut venir de plus
>> profond et donc c'est assez obtus. On ne veut pas en oublier une
>> parce que ça conduit à un plantage et c'est pas vendeur. Et ça
>> peut arriver par la suite avec une nouvelle version de ladite
>> librairie. D'où le choix du catch-all équivalent du défault...

> Il y a plein de façon d'avoir une bibliothèque mal conçue en deho rs
> des exceptions.

Ah que oui ! Mais on a pas trop le choix, il faut vivre avec.
D'où la programmation défensive...



Mais dans un siège, il faut faire une sortie de temps en temps :)

[...]

>> Moi j'aime pas trop les logiciels qui disent "au
>> secours, j'y arrive pas", de plus je fais dans le process
>> automatisé, alors souvent il n'y a personne pour lire le message.

> Un petit mail à l'administrateur, ça fait toujours plaisir :)

Ce genre de truc n'a pas d'email en général.

Exemple vécu (même pas besoin d'un missile, un trieur industriel):

La machine (1 hectare environ) est pilotée directement par des racks OS /9 ou
similaires, mais pour le contrôle de process on met volontiers du PC
(plusieurs rendondants éventuellement) parce que:
- c'est puissant pour pas cher,
- ça permet de faire des IHM évoluées que le client réclame,
- ça peut intégrer une DB Oracle ou Sybase,
- c'est moins impossible à interfacer avec du gros système COBOL (c ôté
compta),
- les développeurs qui connaissent se trouvent plus facilement sur le
marché,
- divers...

En fonctionnement normal, s'il y a un écran, il est dans une armoire et
personne ne le regarde, hors maintenance.
Quand bien même quelqu'un verrait la MessageBox "Erreur d'accès au fi chier
TOTO.DAT", la plupart des opérateurs ne savent même pas ce qu'est un
fichier.



Un signal sonore/lumineux est toujours possible.

Et si le responsable de prod intervient, il va bien comprendre "qu'il y a
une .erde avec l'informatique", mais ensuite ?
Usine bloquée à 2h00 du mat, 500 opérateurs qui poireautent, les ca mionneurs
aussi, le réassort de toute la distribution de la région pour le lend emain
première heure qui est compromis...
Là le responsable réveille son PDG qui réveille celui de la SSII, e t vous
imaginez la suite...

Donc à moins d'un tremblement de terre majeur, le process DOIT se
débrouiller tout seul :o)

Evidemment c'est très dûr à réussir, mais c'est quand même bien de ne pas y
renoncer d'emblée pour se faciliter le codage.
Alors on essaie de blinder au maximum et de récupérer à tout prix t out
problème qui peut l'être d'une manière quelconque.



Je suis d'accord. Ne serait ce que pour des coûts de maintenance et de
qualité de service mais si le pire du pire arrive, dans ce que j'ai
vu, ils se posent pas la question et redémarrent la machine.
Si ça ne marche pas, RAZ du système.

Sur le drone sur lequel je travaillais, les processus étaient monitoré
et si les ressource système étaient épuisées, le système redéma rrait
les services proprement.

Si le système s'était corrompu, un failback relançait une RAZ du
système et si ça ne marchait pas non plus, l'utilisateur avait une cl é
USB permettant de le faire à la mano (plug and boot) ... en supposant
qu'il ait pu atterrir en mode dégradé :)

--
Michael
Avatar
James Kanze
On Jul 7, 2:50 pm, ld wrote:
On 7 juil, 11:17, James Kanze wrote:
> Il n'a de signification pour le client qu'après l'échec
> d'une lecture (et c'est surtout une signification inversée
> -- s'il n'est pas positionné, alors que failbit l'est, c'est
> qu'il y a une erreur de format dans le fichier). La raison
> pourquoi il ne fait jamais de sens qu'il génère une
> exception, c'est précisement parce qu'il ne signifie rien
> pour le client, qu'il peut être positionné d'une façon un
> peu aléatoire, non selon ce qu'on vient de lire (ou qu'on
> vient d'essayer à lire), mais selon ce qui suit dans le
> fichier, et même la façon que istream fonctionne
> internellement.



? Pas tout compris... Si on voit un eofbit, c'est que la fin
de fichier a ete _effectivement_ vue, pas seulement que l'on
s'y trouve.



Pas au niveau du client. Essaie :

int
main()
{
std::istringstream s( "1.23" ) ; // pas de 'n' final !!!
double d ;
s >> d ;
std::cout << s.eof() << std::endl ;
return 0 ;
}

Après le « s >> d », le client n'a pas encore rencontré la fin  ;
il a bien lu son double. Mais normalement, le eofbit sera
positionné.

[...]
je pensais plutot a quelque chose comme:



if (! (readInt(parser) || readFloat(parser) || readDate(parser) ||
readString(parser)))
throw parser.error("syntax error: invalid field value");



note que c'est le parser lui-meme que je "throw", car il
contient les informations sur l'etat courant du parsing
(history, location, remaining, context) qui permet de
detailler le rapport de l'erreur.



Indépendamment de l'utilisation de l'exception, oui, il faut
bien incorporer des informations du parser dans le rapport de
l'erreur. D'habitude, je fais quelque chose du genre :

parser.error( severity ) << "invalid field value: " << value ;

(Voire simplement « error( severity )... », si je suis dans une
fonction du parseur, ce qui est généralement le cas.) Ensuite,
le destructeur du temporaire renvoyé par la fonction error fait
ce qu'il faut -- si on veut une exception, c'est là (eh oui,
dans le destructeur) qu'on la lève.

--
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
James Kanze
On Jul 7, 3:40 pm, Wykaaa wrote:
James Kanze a écrit :



[...]
>> Et si on veut traiter l'exception au plus proche de sa
>> survenue, on ne mettra pas "qui sait quoi de plus..." dans
>> le "try" !



> Ce qui impose que someAction() soit dans un bloc à soi, avec son
> propre portée. Quelque chose comme :



> try {
> MyType var = someAction() ;
> } catch ...



> ne va pas si var doit servir ulterieurement.



je déclare var au niveau de la fonction bien que ça contredise
la règle "déclarer les variables au plus proche de leur
utilisation". Je ne suis pas pour déclarer des variables dans
les blocs try.



D'abord, tu ne peux pas forcément. Si par exemple le type de la
variable n'a pas de constructeur par défaut, ou ne support pas
l'affectation. Mais aussi, en ce faisant, tu passes à côté d'une
des points forts des exceptions dans les constructeurs ; si le
constructeur n'a pas pû correctement construire l'objet, tu n'as
pas d'objet accessible. C'est un avantage assez important pour
justifier le désavantage de l'éloignement du traitement
d'erreur. (Dans ce cas-là, je mets en général tout le
traitement dans une fonction à part, pour pouvoir écrire :

try {
MyType object( initialisateurs ) ;
object.toutLeTraitement() ;
} catch ...

Ce qui limite l'éloignement.)

--
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
James Kanze
On Jul 7, 6:55 pm, Sylvain SF wrote:
James Kanze a écrit :
> La première publication sur la *possibilité* d'exceptions en C++
> date de 1989 ("Exception Handling for C++", de Koenig et
> Stroustrup).



suivi d'autres articles vers 1922-1994 avec des implémentations
concrètes de support d'exception (par setjmp/longjmp).



> Dans la pratique, dans l'industrie, on n'a pu réelement
> commencer à se servir des exceptions que vers 2000.



à s'en servir de manière standard (mots clés try, catch).
je les utilisais (et ce n'était pas un usage isolé) dès
1995-1996 dans tous mes codes C++ (CFront sous MPW/MacOS).



Je me suis servi des exceptions maison (à base de
setjmp/longjmp, avec l'exigeance que tous les objets avec
destructeur dérivent d'une classe spéciale dont le constructeur
les « inscrivait », et du code dépendant de l'implémentation
pour déterminer si l'objet inscrit se trouvait dans la partie de
la pile sautée par le longjmp) dans mon premier projet C++, à
partir de 1992 (mais le projet avait commencé à s'en servir
avant). C'est cette expérience qui m'a rendu un peu sceptique
vis-à-vis des exceptions. (Et le projet a fini par les
abandonner, pour de nombreux raisons.)

--
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
Wykaaa
James Kanze a écrit :
On Jul 7, 3:40 pm, Wykaaa wrote:
James Kanze a écrit :



[...]
Et si on veut traiter l'exception au plus proche de sa
survenue, on ne mettra pas "qui sait quoi de plus..." dans
le "try" !







Ce qui impose que someAction() soit dans un bloc à soi, avec son
propre portée. Quelque chose comme :





try {
MyType var = someAction() ;
} catch ...





ne va pas si var doit servir ulterieurement.





je déclare var au niveau de la fonction bien que ça contredise
la règle "déclarer les variables au plus proche de leur
utilisation". Je ne suis pas pour déclarer des variables dans
les blocs try.



D'abord, tu ne peux pas forcément. Si par exemple le type de la
variable n'a pas de constructeur par défaut, ou ne support pas
l'affectation. Mais aussi, en ce faisant, tu passes à côté d'une
des points forts des exceptions dans les constructeurs ; si le
constructeur n'a pas pû correctement construire l'objet, tu n'as
pas d'objet accessible. C'est un avantage assez important pour
justifier le désavantage de l'éloignement du traitement
d'erreur. (Dans ce cas-là, je mets en général tout le
traitement dans une fonction à part, pour pouvoir écrire :

try {
MyType object( initialisateurs ) ;
object.toutLeTraitement() ;
} catch ...

Ce qui limite l'éloignement.)



Nous sommes d'accord :-)
Avatar
ld
On 8 juil, 10:25, James Kanze wrote:
On Jul 7, 2:50 pm, ld wrote:

> On 7 juil, 11:17, James Kanze wrote:
> > Il n'a de signification pour le client qu'après l'échec
> > d'une lecture (et c'est surtout une signification inversée
> > -- s'il n'est pas positionné, alors que failbit l'est, c'est
> > qu'il y a une erreur de format dans le fichier). La raison
> > pourquoi il ne fait jamais de sens qu'il génère une
> > exception, c'est précisement parce qu'il ne signifie rien
> > pour le client, qu'il peut être positionné d'une façon un
> > peu aléatoire, non selon ce qu'on vient de lire (ou qu'on
> > vient d'essayer à lire), mais selon ce qui suit dans le
> > fichier, et même la façon que istream fonctionne
> > internellement.
> ? Pas tout compris... Si on voit un eofbit, c'est que la fin
> de fichier a ete _effectivement_ vue, pas seulement que l'on
> s'y trouve.

Pas au niveau du client. Essaie :

    int
    main()
    {
        std::istringstream s( "1.23" ) ; // pas de 'n' final !!!
        double d ;
        s >> d ;
        std::cout << s.eof() << std::endl ;
        return 0 ;
    }

Après le « s >> d », le client n'a pas encore rencontré la fi n ;



je ne sais pas ce que tu appelles le "client" dans ce code. Mais il
est clair que le stream a rencontre la fin, ce qui entre dans le cadre
de ce que je disais (c'etait suppose etre un contre exemple?)

il a bien lu son double. Mais normalement, le eofbit sera
positionné.



oui et?

Indépendamment de l'utilisation de l'exception, oui, il faut
bien incorporer des informations du parser dans le rapport de
l'erreur. D'habitude, je fais quelque chose du genre :

    parser.error( severity ) << "invalid field value: " << value ;

(Voire simplement « error( severity )... », si je suis dans une
fonction du parseur, ce qui est généralement le cas.) Ensuite,
le destructeur du temporaire renvoyé par la fonction error fait
ce qu'il faut -- si on veut une exception, c'est là (eh oui,
dans le destructeur) qu'on la lève.



je ne joue pas ce jeux la...

a+, ld.