void f(); // throw (Machin)
Non. C'est plus de l'ordre de la documentation.
Non, c'est plus que de la documentation, parce que c'est enforcé
par le code généré par le compilateur.
void f(); // throw (Machin)
Non. C'est plus de l'ordre de la documentation.
Non, c'est plus que de la documentation, parce que c'est enforcé
par le code généré par le compilateur.
void f(); // throw (Machin)
Non. C'est plus de l'ordre de la documentation.
Non, c'est plus que de la documentation, parce que c'est enforcé
par le code généré par le compilateur.
On 9 Jan 2006 00:48:11 -0800, "kanze" :void f(); // throw (Machin)
Non. C'est plus de l'ordre de la documentation.
Non, c'est plus que de la documentation, parce que c'est
enforcé par le code généré par le compilateur.
Justement : ce que je veux mettre dans mon code, c'est de la
documentation.
Quand j'ai décidé d'utiliser des spécifications d'exceptions
autres que "nothrow", c'est en faisant du code de gestion de
fichiers pour le programme d'installation d'un logiciel. Une
fonction d'ouverture de fichier, par exemple, peut se terminer
de trois manières différentes :
- soit renvoyer un handle sur le fichier ;
- soit lancer une exception "impossible d'ouvrir le fichier"
(l'usage d'une exception ici se justifie par le nombre
d'appels de fonctions qu'il faut "remonter" avant de se
retrouver dans l'interface graphique, seule capable d'afficher
un message d'erreur) ;
- soit un autre type d'erreur (un "new" qui lance une exception,
par exemple), et là j'avoue que je ne sais pas quel est le mieux :
sortir du programme par terminate(), ou remonter dans le main() et
afficher un message d'erreur guère plus explicite.
Donc, en pratique, dans ce cas, mettre une spécification
d'exception ou juste de la documentation, ça ne fait pas
vraiment de différence.
Mais ce dont j'ai réellement besoin, c'est d'indiquer (si
possible dans le .h) les exceptions que la fonction peut
lancer en fonctionnement normal.
On 9 Jan 2006 00:48:11 -0800, "kanze" <kanze@gabi-soft.fr>:
void f(); // throw (Machin)
Non. C'est plus de l'ordre de la documentation.
Non, c'est plus que de la documentation, parce que c'est
enforcé par le code généré par le compilateur.
Justement : ce que je veux mettre dans mon code, c'est de la
documentation.
Quand j'ai décidé d'utiliser des spécifications d'exceptions
autres que "nothrow", c'est en faisant du code de gestion de
fichiers pour le programme d'installation d'un logiciel. Une
fonction d'ouverture de fichier, par exemple, peut se terminer
de trois manières différentes :
- soit renvoyer un handle sur le fichier ;
- soit lancer une exception "impossible d'ouvrir le fichier"
(l'usage d'une exception ici se justifie par le nombre
d'appels de fonctions qu'il faut "remonter" avant de se
retrouver dans l'interface graphique, seule capable d'afficher
un message d'erreur) ;
- soit un autre type d'erreur (un "new" qui lance une exception,
par exemple), et là j'avoue que je ne sais pas quel est le mieux :
sortir du programme par terminate(), ou remonter dans le main() et
afficher un message d'erreur guère plus explicite.
Donc, en pratique, dans ce cas, mettre une spécification
d'exception ou juste de la documentation, ça ne fait pas
vraiment de différence.
Mais ce dont j'ai réellement besoin, c'est d'indiquer (si
possible dans le .h) les exceptions que la fonction peut
lancer en fonctionnement normal.
On 9 Jan 2006 00:48:11 -0800, "kanze" :void f(); // throw (Machin)
Non. C'est plus de l'ordre de la documentation.
Non, c'est plus que de la documentation, parce que c'est
enforcé par le code généré par le compilateur.
Justement : ce que je veux mettre dans mon code, c'est de la
documentation.
Quand j'ai décidé d'utiliser des spécifications d'exceptions
autres que "nothrow", c'est en faisant du code de gestion de
fichiers pour le programme d'installation d'un logiciel. Une
fonction d'ouverture de fichier, par exemple, peut se terminer
de trois manières différentes :
- soit renvoyer un handle sur le fichier ;
- soit lancer une exception "impossible d'ouvrir le fichier"
(l'usage d'une exception ici se justifie par le nombre
d'appels de fonctions qu'il faut "remonter" avant de se
retrouver dans l'interface graphique, seule capable d'afficher
un message d'erreur) ;
- soit un autre type d'erreur (un "new" qui lance une exception,
par exemple), et là j'avoue que je ne sais pas quel est le mieux :
sortir du programme par terminate(), ou remonter dans le main() et
afficher un message d'erreur guère plus explicite.
Donc, en pratique, dans ce cas, mettre une spécification
d'exception ou juste de la documentation, ça ne fait pas
vraiment de différence.
Mais ce dont j'ai réellement besoin, c'est d'indiquer (si
possible dans le .h) les exceptions que la fonction peut
lancer en fonctionnement normal.
J'ai beaucoup de mal à comprendre ce qu'elle apporte, puisque
le résultat, in fine, c'est qu'il ne suffit pas de lire le
prototype pour savoir quelles exceptions on risque de se
prendre sur la tête en appelant la fonction, alors que ça
semblait justement être le but (aha tout à fait louable) à la
base.
Comment ça ? Le prototype indique bien les exceptions possibles,
au moins s'il est bien écrit.
Dans la pratique, on constate qu'il y a peu d'intérêt à y aller
en détail.
Il est très important de savoir si la fonction peut
lever une exception ou non. Une fois qu'on sait qu'elle peut
lever une exception, le ou les types en est rélativement sans
intérêt.
void f( int ) throw() ; // Ne peut pas lever une exception
void g( int ) ; // Peut lever une exception
Et comme j'ai déjà indiqué, le compilateur génère
automatiquement la vérification de cette post-condition de f().
J'ai beaucoup de mal à comprendre ce qu'elle apporte, puisque
le résultat, in fine, c'est qu'il ne suffit pas de lire le
prototype pour savoir quelles exceptions on risque de se
prendre sur la tête en appelant la fonction, alors que ça
semblait justement être le but (aha tout à fait louable) à la
base.
Comment ça ? Le prototype indique bien les exceptions possibles,
au moins s'il est bien écrit.
Dans la pratique, on constate qu'il y a peu d'intérêt à y aller
en détail.
Il est très important de savoir si la fonction peut
lever une exception ou non. Une fois qu'on sait qu'elle peut
lever une exception, le ou les types en est rélativement sans
intérêt.
void f( int ) throw() ; // Ne peut pas lever une exception
void g( int ) ; // Peut lever une exception
Et comme j'ai déjà indiqué, le compilateur génère
automatiquement la vérification de cette post-condition de f().
J'ai beaucoup de mal à comprendre ce qu'elle apporte, puisque
le résultat, in fine, c'est qu'il ne suffit pas de lire le
prototype pour savoir quelles exceptions on risque de se
prendre sur la tête en appelant la fonction, alors que ça
semblait justement être le but (aha tout à fait louable) à la
base.
Comment ça ? Le prototype indique bien les exceptions possibles,
au moins s'il est bien écrit.
Dans la pratique, on constate qu'il y a peu d'intérêt à y aller
en détail.
Il est très important de savoir si la fonction peut
lever une exception ou non. Une fois qu'on sait qu'elle peut
lever une exception, le ou les types en est rélativement sans
intérêt.
void f( int ) throw() ; // Ne peut pas lever une exception
void g( int ) ; // Peut lever une exception
Et comme j'ai déjà indiqué, le compilateur génère
automatiquement la vérification de cette post-condition de f().
J'ai beaucoup de mal à comprendre ce qu'elle apporte,
puisque le résultat, in fine, c'est qu'il ne suffit pas de
lire le prototype pour savoir quelles exceptions on risque
de se prendre sur la tête en appelant la fonction, alors
que ça semblait justement être le but (aha tout à fait
louable) à la base.
Comment ça ? Le prototype indique bien les exceptions
possibles, au moins s'il est bien écrit.
Oui, le jour où on l'écrit. Mais, en mettant ma casquette
d'hérétique, je dirais que le jour où on change la liste
d'exceptions qui peuvent être déclenchées par une méthode, le
compilateur n'apportant aucune aide pour vérifier qu'on n'a
pas oublié d'ajouter l'exception dans la clause throw() de
l'ensemble des méthodes appelantes, l'indication donnée par le
prototype est vouée à devenir fausse, de même que du code C
est voué à devenir un plat de nouilles passé la 10000ième
ligne de code.
Dans la pratique, on constate qu'il y a peu d'intérêt à y
aller en détail.
Oui, à cause du problème ci-dessus,
mais je suis tout d'accord avec Fabien: un problème spécifique
peut avoir son exception spécifique, qu'on ne souhaite pas
forcément traiter au même endroit que les autres.
Du moins est-ce le cas quand on utilise des librairies qui
lèvent joyeusement des exceptions dans des circonstances
presque normales, alors que l'erreur est rattrappable.
Sinon, on se retrouve avec un syndrome du python: le choix
entre le catch-all et le pas de catch du tout parce que de
toute façon, on ne sait jamais quelles exceptions on risque de
lever en appelant une fonction.
Il est très important de savoir si la fonction peut lever
une exception ou non. Une fois qu'on sait qu'elle peut lever
une exception, le ou les types en est rélativement sans
intérêt.
Bah je ne trouve pas. Beaucoup d'erreurs sont rattrappables,
mais il faut se placer au bon layer pour les rattrapper ou
implémenter des retries, et pour ça, on a besoin de savoir qui
peut lever quoi avec certitude.
Mais peut-être qu'on peut simplement résoudre ce problème avec
des exceptions hiérarchisées, je n'ai pas encore joué avec ça.void f( int ) throw() ; // Ne peut pas lever une exception
void g( int ) ; // Peut lever une exception
Et comme j'ai déjà indiqué, le compilateur génère
automatiquement la vérification de cette post-condition de
f().
Oui, mais en run time, non ?
J'ai beaucoup de mal à comprendre ce qu'elle apporte,
puisque le résultat, in fine, c'est qu'il ne suffit pas de
lire le prototype pour savoir quelles exceptions on risque
de se prendre sur la tête en appelant la fonction, alors
que ça semblait justement être le but (aha tout à fait
louable) à la base.
Comment ça ? Le prototype indique bien les exceptions
possibles, au moins s'il est bien écrit.
Oui, le jour où on l'écrit. Mais, en mettant ma casquette
d'hérétique, je dirais que le jour où on change la liste
d'exceptions qui peuvent être déclenchées par une méthode, le
compilateur n'apportant aucune aide pour vérifier qu'on n'a
pas oublié d'ajouter l'exception dans la clause throw() de
l'ensemble des méthodes appelantes, l'indication donnée par le
prototype est vouée à devenir fausse, de même que du code C
est voué à devenir un plat de nouilles passé la 10000ième
ligne de code.
Dans la pratique, on constate qu'il y a peu d'intérêt à y
aller en détail.
Oui, à cause du problème ci-dessus,
mais je suis tout d'accord avec Fabien: un problème spécifique
peut avoir son exception spécifique, qu'on ne souhaite pas
forcément traiter au même endroit que les autres.
Du moins est-ce le cas quand on utilise des librairies qui
lèvent joyeusement des exceptions dans des circonstances
presque normales, alors que l'erreur est rattrappable.
Sinon, on se retrouve avec un syndrome du python: le choix
entre le catch-all et le pas de catch du tout parce que de
toute façon, on ne sait jamais quelles exceptions on risque de
lever en appelant une fonction.
Il est très important de savoir si la fonction peut lever
une exception ou non. Une fois qu'on sait qu'elle peut lever
une exception, le ou les types en est rélativement sans
intérêt.
Bah je ne trouve pas. Beaucoup d'erreurs sont rattrappables,
mais il faut se placer au bon layer pour les rattrapper ou
implémenter des retries, et pour ça, on a besoin de savoir qui
peut lever quoi avec certitude.
Mais peut-être qu'on peut simplement résoudre ce problème avec
des exceptions hiérarchisées, je n'ai pas encore joué avec ça.
void f( int ) throw() ; // Ne peut pas lever une exception
void g( int ) ; // Peut lever une exception
Et comme j'ai déjà indiqué, le compilateur génère
automatiquement la vérification de cette post-condition de
f().
Oui, mais en run time, non ?
J'ai beaucoup de mal à comprendre ce qu'elle apporte,
puisque le résultat, in fine, c'est qu'il ne suffit pas de
lire le prototype pour savoir quelles exceptions on risque
de se prendre sur la tête en appelant la fonction, alors
que ça semblait justement être le but (aha tout à fait
louable) à la base.
Comment ça ? Le prototype indique bien les exceptions
possibles, au moins s'il est bien écrit.
Oui, le jour où on l'écrit. Mais, en mettant ma casquette
d'hérétique, je dirais que le jour où on change la liste
d'exceptions qui peuvent être déclenchées par une méthode, le
compilateur n'apportant aucune aide pour vérifier qu'on n'a
pas oublié d'ajouter l'exception dans la clause throw() de
l'ensemble des méthodes appelantes, l'indication donnée par le
prototype est vouée à devenir fausse, de même que du code C
est voué à devenir un plat de nouilles passé la 10000ième
ligne de code.
Dans la pratique, on constate qu'il y a peu d'intérêt à y
aller en détail.
Oui, à cause du problème ci-dessus,
mais je suis tout d'accord avec Fabien: un problème spécifique
peut avoir son exception spécifique, qu'on ne souhaite pas
forcément traiter au même endroit que les autres.
Du moins est-ce le cas quand on utilise des librairies qui
lèvent joyeusement des exceptions dans des circonstances
presque normales, alors que l'erreur est rattrappable.
Sinon, on se retrouve avec un syndrome du python: le choix
entre le catch-all et le pas de catch du tout parce que de
toute façon, on ne sait jamais quelles exceptions on risque de
lever en appelant une fonction.
Il est très important de savoir si la fonction peut lever
une exception ou non. Une fois qu'on sait qu'elle peut lever
une exception, le ou les types en est rélativement sans
intérêt.
Bah je ne trouve pas. Beaucoup d'erreurs sont rattrappables,
mais il faut se placer au bon layer pour les rattrapper ou
implémenter des retries, et pour ça, on a besoin de savoir qui
peut lever quoi avec certitude.
Mais peut-être qu'on peut simplement résoudre ce problème avec
des exceptions hiérarchisées, je n'ai pas encore joué avec ça.void f( int ) throw() ; // Ne peut pas lever une exception
void g( int ) ; // Peut lever une exception
Et comme j'ai déjà indiqué, le compilateur génère
automatiquement la vérification de cette post-condition de
f().
Oui, mais en run time, non ?
Dans la pratique, on constate qu'il y a peu d'intérêt à y
aller en détail.
Oui, à cause du problème ci-dessus,
Non. Il y a peu d'intérêt à y aller en détail parce que ça ne
change rien pour l'utilisateur, et que ça pose des problèmes
dans l'hiérarchie des appels.
L'intérêt d'utiliser une
exception, c'est que le traitement de l'erreur se trouve à un
endroit bien éloigné de sa détection. Et que la seule chose qui
intéresse toutes ces fonctions intermédiaire, c'est est qu'il y
a un cheminement supplémentaire à prendre en compte, à cause
d'une exception possible, ou non.
Par exemple ? Je ne suis pas sûr de comprendre ce que tu essaies
de dire. C'est évident qu'une manque de mémoire doit provoquer
un type d'exception différent qu'une erreur de lecture de
disque. Selon le cas, c'est même possible qu'on traite ces deux
types d'erreur à des niveaux différents. Mais pour toutes les
fonctions intermédiaires, où est la différence ?
Du moins est-ce le cas quand on utilise des librairies qui
lèvent joyeusement des exceptions dans des circonstances
presque normales, alors que l'erreur est rattrappable.
Si on abuse d'une facilité, c'est évident qu'on aurait des
problèmes supplémentaire:-).
Je crois que dans le cas de Fabien, il a un type bien précis
d'exception, pour un type d'erreur bien précis, qu'il peut
traiter à part, à un niveau plus bas. Il ne prétend pas que sa
fonction ne pourrait jamais lever d'autre types d'exception,
genre std::bad_alloc.
Ce qu'il a, ce n'est pas un contrat en ce qui concerne les types
d'exception possibles. Ce qu'il a, c'est un contrat en ce qui
concerne le comportement dans un cas bien précis. Si ce cas se
présente, ET il n'y a pas d'autre erreur, il y aurait une
exception d'un type défini par le contrat.
Je crois que tu vois le problème à l'envers. Le problème que tu
sembles décrire, ce n'est pas si une fonction f peut lever une
exception X. Le problème est si le cas Y va provoquer une
exception X. Et le problème annex : est-ce que l'exception X ne
peut se produire QUE dans le cas Y ?
La spécification d'exception n'apporte rien ici. À vrai dire,
j'ai du mal à imaginer une construction du langage qui puisse y
apporter quelque chose.
J'aurais préféré, moi-aussi, une vérifcation statique. Mais il
faut dire qu'il n'y a erreur que si l'exception est réelement
levée. Exiger une vérification statique aurait exiger ou bien
des moyens au delà de ce que savent faire les compilateurs
aujourd'hui, pour detecter tous les cas d'erreur
ou bien du
code supplémentaire (des try/catch en plus) écrit par le
programmeur quand il sait qu'aucune exception n'est possible.
Personnellement, le deuxième ne me gène pas trop (surtout, que
les cas où il serait nécessaire sont bien rare).
Mais c'est
contraire à la philosophie de C++, et n'était pas acceptable au
comité.
Dans la pratique, on constate qu'il y a peu d'intérêt à y
aller en détail.
Oui, à cause du problème ci-dessus,
Non. Il y a peu d'intérêt à y aller en détail parce que ça ne
change rien pour l'utilisateur, et que ça pose des problèmes
dans l'hiérarchie des appels.
L'intérêt d'utiliser une
exception, c'est que le traitement de l'erreur se trouve à un
endroit bien éloigné de sa détection. Et que la seule chose qui
intéresse toutes ces fonctions intermédiaire, c'est est qu'il y
a un cheminement supplémentaire à prendre en compte, à cause
d'une exception possible, ou non.
Par exemple ? Je ne suis pas sûr de comprendre ce que tu essaies
de dire. C'est évident qu'une manque de mémoire doit provoquer
un type d'exception différent qu'une erreur de lecture de
disque. Selon le cas, c'est même possible qu'on traite ces deux
types d'erreur à des niveaux différents. Mais pour toutes les
fonctions intermédiaires, où est la différence ?
Du moins est-ce le cas quand on utilise des librairies qui
lèvent joyeusement des exceptions dans des circonstances
presque normales, alors que l'erreur est rattrappable.
Si on abuse d'une facilité, c'est évident qu'on aurait des
problèmes supplémentaire:-).
Je crois que dans le cas de Fabien, il a un type bien précis
d'exception, pour un type d'erreur bien précis, qu'il peut
traiter à part, à un niveau plus bas. Il ne prétend pas que sa
fonction ne pourrait jamais lever d'autre types d'exception,
genre std::bad_alloc.
Ce qu'il a, ce n'est pas un contrat en ce qui concerne les types
d'exception possibles. Ce qu'il a, c'est un contrat en ce qui
concerne le comportement dans un cas bien précis. Si ce cas se
présente, ET il n'y a pas d'autre erreur, il y aurait une
exception d'un type défini par le contrat.
Je crois que tu vois le problème à l'envers. Le problème que tu
sembles décrire, ce n'est pas si une fonction f peut lever une
exception X. Le problème est si le cas Y va provoquer une
exception X. Et le problème annex : est-ce que l'exception X ne
peut se produire QUE dans le cas Y ?
La spécification d'exception n'apporte rien ici. À vrai dire,
j'ai du mal à imaginer une construction du langage qui puisse y
apporter quelque chose.
J'aurais préféré, moi-aussi, une vérifcation statique. Mais il
faut dire qu'il n'y a erreur que si l'exception est réelement
levée. Exiger une vérification statique aurait exiger ou bien
des moyens au delà de ce que savent faire les compilateurs
aujourd'hui, pour detecter tous les cas d'erreur
ou bien du
code supplémentaire (des try/catch en plus) écrit par le
programmeur quand il sait qu'aucune exception n'est possible.
Personnellement, le deuxième ne me gène pas trop (surtout, que
les cas où il serait nécessaire sont bien rare).
Mais c'est
contraire à la philosophie de C++, et n'était pas acceptable au
comité.
Dans la pratique, on constate qu'il y a peu d'intérêt à y
aller en détail.
Oui, à cause du problème ci-dessus,
Non. Il y a peu d'intérêt à y aller en détail parce que ça ne
change rien pour l'utilisateur, et que ça pose des problèmes
dans l'hiérarchie des appels.
L'intérêt d'utiliser une
exception, c'est que le traitement de l'erreur se trouve à un
endroit bien éloigné de sa détection. Et que la seule chose qui
intéresse toutes ces fonctions intermédiaire, c'est est qu'il y
a un cheminement supplémentaire à prendre en compte, à cause
d'une exception possible, ou non.
Par exemple ? Je ne suis pas sûr de comprendre ce que tu essaies
de dire. C'est évident qu'une manque de mémoire doit provoquer
un type d'exception différent qu'une erreur de lecture de
disque. Selon le cas, c'est même possible qu'on traite ces deux
types d'erreur à des niveaux différents. Mais pour toutes les
fonctions intermédiaires, où est la différence ?
Du moins est-ce le cas quand on utilise des librairies qui
lèvent joyeusement des exceptions dans des circonstances
presque normales, alors que l'erreur est rattrappable.
Si on abuse d'une facilité, c'est évident qu'on aurait des
problèmes supplémentaire:-).
Je crois que dans le cas de Fabien, il a un type bien précis
d'exception, pour un type d'erreur bien précis, qu'il peut
traiter à part, à un niveau plus bas. Il ne prétend pas que sa
fonction ne pourrait jamais lever d'autre types d'exception,
genre std::bad_alloc.
Ce qu'il a, ce n'est pas un contrat en ce qui concerne les types
d'exception possibles. Ce qu'il a, c'est un contrat en ce qui
concerne le comportement dans un cas bien précis. Si ce cas se
présente, ET il n'y a pas d'autre erreur, il y aurait une
exception d'un type défini par le contrat.
Je crois que tu vois le problème à l'envers. Le problème que tu
sembles décrire, ce n'est pas si une fonction f peut lever une
exception X. Le problème est si le cas Y va provoquer une
exception X. Et le problème annex : est-ce que l'exception X ne
peut se produire QUE dans le cas Y ?
La spécification d'exception n'apporte rien ici. À vrai dire,
j'ai du mal à imaginer une construction du langage qui puisse y
apporter quelque chose.
J'aurais préféré, moi-aussi, une vérifcation statique. Mais il
faut dire qu'il n'y a erreur que si l'exception est réelement
levée. Exiger une vérification statique aurait exiger ou bien
des moyens au delà de ce que savent faire les compilateurs
aujourd'hui, pour detecter tous les cas d'erreur
ou bien du
code supplémentaire (des try/catch en plus) écrit par le
programmeur quand il sait qu'aucune exception n'est possible.
Personnellement, le deuxième ne me gène pas trop (surtout, que
les cas où il serait nécessaire sont bien rare).
Mais c'est
contraire à la philosophie de C++, et n'était pas acceptable au
comité.
Dans la pratique, on constate qu'il y a peu d'intérêt à y
aller en détail.
Oui, à cause du problème ci-dessus,
Non. Il y a peu d'intérêt à y aller en détail parce que ça
ne change rien pour l'utilisateur, et que ça pose des
problèmes dans l'hiérarchie des appels.
À mon sens, ça change beaucoup de choses pour l'utilisateur.
Prenons un exemple. Disons que je suis en train de réaliser un
logiciel, qui doit échanger des informations avec des serveurs
distants, et que tout ce petit monde se cause en XML sur HTTP.
J'envoie donc des requêtes en POST contenant une description
de ce que je veux en XML, et je reçois des réponses en XML que
je dois interpréter quand tout va bien.
Il y a bien sûr plusieurs découpages en couches possibles.
L'un d'entre eux est le suivant:
// niveau le plus haut
const parsed_reply_t * send_request( std::array < arg_t > * args );
// une couche en-dessous
const dom_tree_t * req::receive_and_parse_reply();
void req::encode_and_send_request( const dom_tree_t * );
// encore une couche en-dessous
const std::string http_req::receive_reply();
void http_req::send_request( const std::string content );
// encore en-dessous
void tcp_socket::send( const void * data, ssize_t length );
ssize_t tcp_socket::recv( const void * buffer, ssize_t max_length );
Si j'ai une erreur réseau, la classe tcp_socket va
légitimement lever une exception, par exemple une
network_error. La classe http_req, qui utilise les tcp_socket,
risque donc dans ses méthodes de lever network_error. Mais
elle peut aussi lever d'autres exceptions, par exemple
http_parse_error si le serveur en face envoie n'importe quoi.
La méthode receive_and_parse_reply de la classe req, peut donc
lever ces deux exceptions-là, plus xml_parse_error si le HTTP
est correct mais le XML renvoyé n'a pas le bon format. Et bien
sûr, quasiment tout le monde peut lever std::bad_alloc.
Si j'implémente send_request, quand j'appelle une méthode de
req, je ne suis pas intéressé seulement par savoir si elle
peut ou non lever des exceptions. Je veux savoir lesquelles:
si j'ai une bad_alloc, il est probable, dans la plupart des
applications, que je vais laisser tomber et quitter l'appli.
Par contre, si j'ai une network_error, je vais peut-être
vouloir essayer un autre serveur, ou ouvrir une fenêtre disant
qu'il faut vérifier que les connexions réseau sont ok. Si j'ai
une xml_parse_error, je vais peut-être vouloir envoyer un mail
à Bref, je vais exécuter des
actions différentes en fonction des exceptions qui arrivent,
je ne vais pas me contenter de faire du code exception safe.
Mais pour ça, j'ai besoin de connaître au moins les familles
d'exceptions qui risquent d'être déclenchées.
L'intérêt d'utiliser une exception, c'est que le traitement
de l'erreur se trouve à un endroit bien éloigné de sa
détection. Et que la seule chose qui intéresse toutes ces
fonctions intermédiaire, c'est est qu'il y a un cheminement
supplémentaire à prendre en compte, à cause d'une exception
possible, ou non.
Plusieurs cheminements, en fonction de la nature de
l'exception : je traite tel problème de telle manière, tel
autre problème de telle autre manière, ou je ne sais pas
faire.
Par exemple ? Je ne suis pas sûr de comprendre ce que tu
essaies de dire. C'est évident qu'une manque de mémoire doit
provoquer un type d'exception différent qu'une erreur de
lecture de disque. Selon le cas, c'est même possible qu'on
traite ces deux types d'erreur à des niveaux différents.
Mais pour toutes les fonctions intermédiaires, où est la
différence ?
Certaines fonctions intermédiaires peuvent avoir la
responsabilité de traiter une partie des erreurs, et pas les
autres erreurs. Mais pour ça, encore faut-il qu'elles sachents
quelles erreurs elles sont suceptibles de voir passer.
Du moins est-ce le cas quand on utilise des librairies qui
lèvent joyeusement des exceptions dans des circonstances
presque normales, alors que l'erreur est rattrappable.
Si on abuse d'une facilité, c'est évident qu'on aurait des
problèmes supplémentaire:-).
Oui, mais malheureusement, on ne choisit pas toujours :=(Je crois que dans le cas de Fabien, il a un type bien précis
d'exception, pour un type d'erreur bien précis, qu'il peut
traiter à part, à un niveau plus bas. Il ne prétend pas que
sa fonction ne pourrait jamais lever d'autre types
d'exception, genre std::bad_alloc.
Ce qu'il a, ce n'est pas un contrat en ce qui concerne les
types d'exception possibles. Ce qu'il a, c'est un contrat en
ce qui concerne le comportement dans un cas bien précis. Si
ce cas se présente, ET il n'y a pas d'autre erreur, il y
aurait une exception d'un type défini par le contrat.
Tout à fait. Mais ce que nous déplorons, c'est qu'il n'y ait
pas moyen d'établir un contrat plus précis, couvrant toutes
les erreurs de manière exhaustive.
Dans mon exemple ci-dessus, mon logiciel peut avoir pour
mission d'essayer tous les serveurs HTTP indiqués dans un
fichier de configuration jusqu'à épuisement, et par contre, de
laisser complètement tomber s'il se rend compte que le code
déployé sur ces serveurs génère du XML mal formé. Pour ce
faire, il a donc besoin de connaître exhaustivement les
erreurs qui peuvent lui tomber sur la tête en provenance des
classes gérant la partie réseau et la partie HTTP : il ne doit
laisser passer que certaines erreurs venant de là, après les
avoir triées sur le volet.Je crois que tu vois le problème à l'envers. Le problème que
tu sembles décrire, ce n'est pas si une fonction f peut
lever une exception X. Le problème est si le cas Y va
provoquer une exception X. Et le problème annex : est-ce que
l'exception X ne peut se produire QUE dans le cas Y ?
Pas d'accord. Mon souci n'est pas d'imaginer la liste de tous
les problèmes qui peuvent se produire et de demander quelle
est l'exception associée. Mon souci est de dire en appelant
cette fonction, qu'est-ce que je risque ? C'est à elle de
déclarer l'ensemble des situations d'erreur, pas à moi de
chercher à les deviner. Et comme cet ensemble évolue dans le
temps au fil des versions, je trouve que se contenter de le
documenter est insuffisant, j'aimerais que le compilateur
puisse m'aider un peu et en me disant attention, il y a un
nouveau cas d'erreur que tu n'adresses pas, et tu n'as pas non
plus déclaré que tu ne l'adressais pas dans ton proto, bref tu
es passé à côté.
La spécification d'exception n'apporte rien ici. À vrai
dire, j'ai du mal à imaginer une construction du langage qui
puisse y apporter quelque chose.
Je crois que ça existe en java, mais je ne connais pas les
détails de la chose.
Évidemment, c'est plus facile avec ce langage où la moitié du
travail se fait à l'exécution. Je ne pense pas qu'on puisse
avoir une solution 100% garantie en langage compilé, mais il
doit y avoir moyen de traiter la plus grosse partie du
problème.
J'aurais préféré, moi-aussi, une vérifcation statique. Mais
il faut dire qu'il n'y a erreur que si l'exception est
réelement levée. Exiger une vérification statique aurait
exiger ou bien des moyens au delà de ce que savent faire les
compilateurs aujourd'hui, pour detecter tous les cas
d'erreur
Au niveau du seul compilateur, en effet. Mais avec un coup de
main de l'éditeur de liens, je pense qu'on doit pouvoir gérer
beaucoup plus de choses.
ou bien du code supplémentaire (des try/catch en plus) écrit
par le programmeur quand il sait qu'aucune exception n'est
possible. Personnellement, le deuxième ne me gène pas trop
(surtout, que les cas où il serait nécessaire sont bien
rare).
Tout à fait.Mais c'est contraire à la philosophie de C++, et n'était pas
acceptable au comité.
C'est bien triste :=) Je ne comprends pas très bien cette
philosophie,
le choix fait pour les exceptions (ceinture mais pas
bretelles) me paraît incohérent par rapport au choix réalisé
par rapport au mot clé const (ceinture et bretelles).
Pour les exceptions, on se contente de la promesse de celui
qui a écrit le prototype de la fonction, et au pire, s'il ne
la tient pas, on relève vaguement l'erreur lors de
l'exécution, mais on ne s'émeut pas plus que ça de le voir
appeler des sous-fonctions qui n'ont pas pris le même
engagement que lui concernant la liste des exceptions
déclenchées.
Pour le traitement des objets constants, au contraire, si le
programmeur s'engage à ne pas écrire sur un objet, on le
traîne sur le banc d'infâmie s'il a le malheur d'appeler une
sous-fonction qui ne prend pas le même engagement.
Certes, on a mis à sa disposition le const_cast pour
s'auto-amnistier, mais, au moins, on sait que quand on écrit
un const_cast, on prend la responsabilité de ce qui va se
passer. Et je trouve que c'est la meilleure option : on est
libre de faire ce qu'on veut, mais par défaut, on est protégé
contre 90% des situations à problèmes.
Dans la pratique, on constate qu'il y a peu d'intérêt à y
aller en détail.
Oui, à cause du problème ci-dessus,
Non. Il y a peu d'intérêt à y aller en détail parce que ça
ne change rien pour l'utilisateur, et que ça pose des
problèmes dans l'hiérarchie des appels.
À mon sens, ça change beaucoup de choses pour l'utilisateur.
Prenons un exemple. Disons que je suis en train de réaliser un
logiciel, qui doit échanger des informations avec des serveurs
distants, et que tout ce petit monde se cause en XML sur HTTP.
J'envoie donc des requêtes en POST contenant une description
de ce que je veux en XML, et je reçois des réponses en XML que
je dois interpréter quand tout va bien.
Il y a bien sûr plusieurs découpages en couches possibles.
L'un d'entre eux est le suivant:
// niveau le plus haut
const parsed_reply_t * send_request( std::array < arg_t > * args );
// une couche en-dessous
const dom_tree_t * req::receive_and_parse_reply();
void req::encode_and_send_request( const dom_tree_t * );
// encore une couche en-dessous
const std::string http_req::receive_reply();
void http_req::send_request( const std::string content );
// encore en-dessous
void tcp_socket::send( const void * data, ssize_t length );
ssize_t tcp_socket::recv( const void * buffer, ssize_t max_length );
Si j'ai une erreur réseau, la classe tcp_socket va
légitimement lever une exception, par exemple une
network_error. La classe http_req, qui utilise les tcp_socket,
risque donc dans ses méthodes de lever network_error. Mais
elle peut aussi lever d'autres exceptions, par exemple
http_parse_error si le serveur en face envoie n'importe quoi.
La méthode receive_and_parse_reply de la classe req, peut donc
lever ces deux exceptions-là, plus xml_parse_error si le HTTP
est correct mais le XML renvoyé n'a pas le bon format. Et bien
sûr, quasiment tout le monde peut lever std::bad_alloc.
Si j'implémente send_request, quand j'appelle une méthode de
req, je ne suis pas intéressé seulement par savoir si elle
peut ou non lever des exceptions. Je veux savoir lesquelles:
si j'ai une bad_alloc, il est probable, dans la plupart des
applications, que je vais laisser tomber et quitter l'appli.
Par contre, si j'ai une network_error, je vais peut-être
vouloir essayer un autre serveur, ou ouvrir une fenêtre disant
qu'il faut vérifier que les connexions réseau sont ok. Si j'ai
une xml_parse_error, je vais peut-être vouloir envoyer un mail
à admin@le_serveur_qui_fait_nimp. Bref, je vais exécuter des
actions différentes en fonction des exceptions qui arrivent,
je ne vais pas me contenter de faire du code exception safe.
Mais pour ça, j'ai besoin de connaître au moins les familles
d'exceptions qui risquent d'être déclenchées.
L'intérêt d'utiliser une exception, c'est que le traitement
de l'erreur se trouve à un endroit bien éloigné de sa
détection. Et que la seule chose qui intéresse toutes ces
fonctions intermédiaire, c'est est qu'il y a un cheminement
supplémentaire à prendre en compte, à cause d'une exception
possible, ou non.
Plusieurs cheminements, en fonction de la nature de
l'exception : je traite tel problème de telle manière, tel
autre problème de telle autre manière, ou je ne sais pas
faire.
Par exemple ? Je ne suis pas sûr de comprendre ce que tu
essaies de dire. C'est évident qu'une manque de mémoire doit
provoquer un type d'exception différent qu'une erreur de
lecture de disque. Selon le cas, c'est même possible qu'on
traite ces deux types d'erreur à des niveaux différents.
Mais pour toutes les fonctions intermédiaires, où est la
différence ?
Certaines fonctions intermédiaires peuvent avoir la
responsabilité de traiter une partie des erreurs, et pas les
autres erreurs. Mais pour ça, encore faut-il qu'elles sachents
quelles erreurs elles sont suceptibles de voir passer.
Du moins est-ce le cas quand on utilise des librairies qui
lèvent joyeusement des exceptions dans des circonstances
presque normales, alors que l'erreur est rattrappable.
Si on abuse d'une facilité, c'est évident qu'on aurait des
problèmes supplémentaire:-).
Oui, mais malheureusement, on ne choisit pas toujours :=(
Je crois que dans le cas de Fabien, il a un type bien précis
d'exception, pour un type d'erreur bien précis, qu'il peut
traiter à part, à un niveau plus bas. Il ne prétend pas que
sa fonction ne pourrait jamais lever d'autre types
d'exception, genre std::bad_alloc.
Ce qu'il a, ce n'est pas un contrat en ce qui concerne les
types d'exception possibles. Ce qu'il a, c'est un contrat en
ce qui concerne le comportement dans un cas bien précis. Si
ce cas se présente, ET il n'y a pas d'autre erreur, il y
aurait une exception d'un type défini par le contrat.
Tout à fait. Mais ce que nous déplorons, c'est qu'il n'y ait
pas moyen d'établir un contrat plus précis, couvrant toutes
les erreurs de manière exhaustive.
Dans mon exemple ci-dessus, mon logiciel peut avoir pour
mission d'essayer tous les serveurs HTTP indiqués dans un
fichier de configuration jusqu'à épuisement, et par contre, de
laisser complètement tomber s'il se rend compte que le code
déployé sur ces serveurs génère du XML mal formé. Pour ce
faire, il a donc besoin de connaître exhaustivement les
erreurs qui peuvent lui tomber sur la tête en provenance des
classes gérant la partie réseau et la partie HTTP : il ne doit
laisser passer que certaines erreurs venant de là, après les
avoir triées sur le volet.
Je crois que tu vois le problème à l'envers. Le problème que
tu sembles décrire, ce n'est pas si une fonction f peut
lever une exception X. Le problème est si le cas Y va
provoquer une exception X. Et le problème annex : est-ce que
l'exception X ne peut se produire QUE dans le cas Y ?
Pas d'accord. Mon souci n'est pas d'imaginer la liste de tous
les problèmes qui peuvent se produire et de demander quelle
est l'exception associée. Mon souci est de dire en appelant
cette fonction, qu'est-ce que je risque ? C'est à elle de
déclarer l'ensemble des situations d'erreur, pas à moi de
chercher à les deviner. Et comme cet ensemble évolue dans le
temps au fil des versions, je trouve que se contenter de le
documenter est insuffisant, j'aimerais que le compilateur
puisse m'aider un peu et en me disant attention, il y a un
nouveau cas d'erreur que tu n'adresses pas, et tu n'as pas non
plus déclaré que tu ne l'adressais pas dans ton proto, bref tu
es passé à côté.
La spécification d'exception n'apporte rien ici. À vrai
dire, j'ai du mal à imaginer une construction du langage qui
puisse y apporter quelque chose.
Je crois que ça existe en java, mais je ne connais pas les
détails de la chose.
Évidemment, c'est plus facile avec ce langage où la moitié du
travail se fait à l'exécution. Je ne pense pas qu'on puisse
avoir une solution 100% garantie en langage compilé, mais il
doit y avoir moyen de traiter la plus grosse partie du
problème.
J'aurais préféré, moi-aussi, une vérifcation statique. Mais
il faut dire qu'il n'y a erreur que si l'exception est
réelement levée. Exiger une vérification statique aurait
exiger ou bien des moyens au delà de ce que savent faire les
compilateurs aujourd'hui, pour detecter tous les cas
d'erreur
Au niveau du seul compilateur, en effet. Mais avec un coup de
main de l'éditeur de liens, je pense qu'on doit pouvoir gérer
beaucoup plus de choses.
ou bien du code supplémentaire (des try/catch en plus) écrit
par le programmeur quand il sait qu'aucune exception n'est
possible. Personnellement, le deuxième ne me gène pas trop
(surtout, que les cas où il serait nécessaire sont bien
rare).
Tout à fait.
Mais c'est contraire à la philosophie de C++, et n'était pas
acceptable au comité.
C'est bien triste :=) Je ne comprends pas très bien cette
philosophie,
le choix fait pour les exceptions (ceinture mais pas
bretelles) me paraît incohérent par rapport au choix réalisé
par rapport au mot clé const (ceinture et bretelles).
Pour les exceptions, on se contente de la promesse de celui
qui a écrit le prototype de la fonction, et au pire, s'il ne
la tient pas, on relève vaguement l'erreur lors de
l'exécution, mais on ne s'émeut pas plus que ça de le voir
appeler des sous-fonctions qui n'ont pas pris le même
engagement que lui concernant la liste des exceptions
déclenchées.
Pour le traitement des objets constants, au contraire, si le
programmeur s'engage à ne pas écrire sur un objet, on le
traîne sur le banc d'infâmie s'il a le malheur d'appeler une
sous-fonction qui ne prend pas le même engagement.
Certes, on a mis à sa disposition le const_cast pour
s'auto-amnistier, mais, au moins, on sait que quand on écrit
un const_cast, on prend la responsabilité de ce qui va se
passer. Et je trouve que c'est la meilleure option : on est
libre de faire ce qu'on veut, mais par défaut, on est protégé
contre 90% des situations à problèmes.
Dans la pratique, on constate qu'il y a peu d'intérêt à y
aller en détail.
Oui, à cause du problème ci-dessus,
Non. Il y a peu d'intérêt à y aller en détail parce que ça
ne change rien pour l'utilisateur, et que ça pose des
problèmes dans l'hiérarchie des appels.
À mon sens, ça change beaucoup de choses pour l'utilisateur.
Prenons un exemple. Disons que je suis en train de réaliser un
logiciel, qui doit échanger des informations avec des serveurs
distants, et que tout ce petit monde se cause en XML sur HTTP.
J'envoie donc des requêtes en POST contenant une description
de ce que je veux en XML, et je reçois des réponses en XML que
je dois interpréter quand tout va bien.
Il y a bien sûr plusieurs découpages en couches possibles.
L'un d'entre eux est le suivant:
// niveau le plus haut
const parsed_reply_t * send_request( std::array < arg_t > * args );
// une couche en-dessous
const dom_tree_t * req::receive_and_parse_reply();
void req::encode_and_send_request( const dom_tree_t * );
// encore une couche en-dessous
const std::string http_req::receive_reply();
void http_req::send_request( const std::string content );
// encore en-dessous
void tcp_socket::send( const void * data, ssize_t length );
ssize_t tcp_socket::recv( const void * buffer, ssize_t max_length );
Si j'ai une erreur réseau, la classe tcp_socket va
légitimement lever une exception, par exemple une
network_error. La classe http_req, qui utilise les tcp_socket,
risque donc dans ses méthodes de lever network_error. Mais
elle peut aussi lever d'autres exceptions, par exemple
http_parse_error si le serveur en face envoie n'importe quoi.
La méthode receive_and_parse_reply de la classe req, peut donc
lever ces deux exceptions-là, plus xml_parse_error si le HTTP
est correct mais le XML renvoyé n'a pas le bon format. Et bien
sûr, quasiment tout le monde peut lever std::bad_alloc.
Si j'implémente send_request, quand j'appelle une méthode de
req, je ne suis pas intéressé seulement par savoir si elle
peut ou non lever des exceptions. Je veux savoir lesquelles:
si j'ai une bad_alloc, il est probable, dans la plupart des
applications, que je vais laisser tomber et quitter l'appli.
Par contre, si j'ai une network_error, je vais peut-être
vouloir essayer un autre serveur, ou ouvrir une fenêtre disant
qu'il faut vérifier que les connexions réseau sont ok. Si j'ai
une xml_parse_error, je vais peut-être vouloir envoyer un mail
à Bref, je vais exécuter des
actions différentes en fonction des exceptions qui arrivent,
je ne vais pas me contenter de faire du code exception safe.
Mais pour ça, j'ai besoin de connaître au moins les familles
d'exceptions qui risquent d'être déclenchées.
L'intérêt d'utiliser une exception, c'est que le traitement
de l'erreur se trouve à un endroit bien éloigné de sa
détection. Et que la seule chose qui intéresse toutes ces
fonctions intermédiaire, c'est est qu'il y a un cheminement
supplémentaire à prendre en compte, à cause d'une exception
possible, ou non.
Plusieurs cheminements, en fonction de la nature de
l'exception : je traite tel problème de telle manière, tel
autre problème de telle autre manière, ou je ne sais pas
faire.
Par exemple ? Je ne suis pas sûr de comprendre ce que tu
essaies de dire. C'est évident qu'une manque de mémoire doit
provoquer un type d'exception différent qu'une erreur de
lecture de disque. Selon le cas, c'est même possible qu'on
traite ces deux types d'erreur à des niveaux différents.
Mais pour toutes les fonctions intermédiaires, où est la
différence ?
Certaines fonctions intermédiaires peuvent avoir la
responsabilité de traiter une partie des erreurs, et pas les
autres erreurs. Mais pour ça, encore faut-il qu'elles sachents
quelles erreurs elles sont suceptibles de voir passer.
Du moins est-ce le cas quand on utilise des librairies qui
lèvent joyeusement des exceptions dans des circonstances
presque normales, alors que l'erreur est rattrappable.
Si on abuse d'une facilité, c'est évident qu'on aurait des
problèmes supplémentaire:-).
Oui, mais malheureusement, on ne choisit pas toujours :=(Je crois que dans le cas de Fabien, il a un type bien précis
d'exception, pour un type d'erreur bien précis, qu'il peut
traiter à part, à un niveau plus bas. Il ne prétend pas que
sa fonction ne pourrait jamais lever d'autre types
d'exception, genre std::bad_alloc.
Ce qu'il a, ce n'est pas un contrat en ce qui concerne les
types d'exception possibles. Ce qu'il a, c'est un contrat en
ce qui concerne le comportement dans un cas bien précis. Si
ce cas se présente, ET il n'y a pas d'autre erreur, il y
aurait une exception d'un type défini par le contrat.
Tout à fait. Mais ce que nous déplorons, c'est qu'il n'y ait
pas moyen d'établir un contrat plus précis, couvrant toutes
les erreurs de manière exhaustive.
Dans mon exemple ci-dessus, mon logiciel peut avoir pour
mission d'essayer tous les serveurs HTTP indiqués dans un
fichier de configuration jusqu'à épuisement, et par contre, de
laisser complètement tomber s'il se rend compte que le code
déployé sur ces serveurs génère du XML mal formé. Pour ce
faire, il a donc besoin de connaître exhaustivement les
erreurs qui peuvent lui tomber sur la tête en provenance des
classes gérant la partie réseau et la partie HTTP : il ne doit
laisser passer que certaines erreurs venant de là, après les
avoir triées sur le volet.Je crois que tu vois le problème à l'envers. Le problème que
tu sembles décrire, ce n'est pas si une fonction f peut
lever une exception X. Le problème est si le cas Y va
provoquer une exception X. Et le problème annex : est-ce que
l'exception X ne peut se produire QUE dans le cas Y ?
Pas d'accord. Mon souci n'est pas d'imaginer la liste de tous
les problèmes qui peuvent se produire et de demander quelle
est l'exception associée. Mon souci est de dire en appelant
cette fonction, qu'est-ce que je risque ? C'est à elle de
déclarer l'ensemble des situations d'erreur, pas à moi de
chercher à les deviner. Et comme cet ensemble évolue dans le
temps au fil des versions, je trouve que se contenter de le
documenter est insuffisant, j'aimerais que le compilateur
puisse m'aider un peu et en me disant attention, il y a un
nouveau cas d'erreur que tu n'adresses pas, et tu n'as pas non
plus déclaré que tu ne l'adressais pas dans ton proto, bref tu
es passé à côté.
La spécification d'exception n'apporte rien ici. À vrai
dire, j'ai du mal à imaginer une construction du langage qui
puisse y apporter quelque chose.
Je crois que ça existe en java, mais je ne connais pas les
détails de la chose.
Évidemment, c'est plus facile avec ce langage où la moitié du
travail se fait à l'exécution. Je ne pense pas qu'on puisse
avoir une solution 100% garantie en langage compilé, mais il
doit y avoir moyen de traiter la plus grosse partie du
problème.
J'aurais préféré, moi-aussi, une vérifcation statique. Mais
il faut dire qu'il n'y a erreur que si l'exception est
réelement levée. Exiger une vérification statique aurait
exiger ou bien des moyens au delà de ce que savent faire les
compilateurs aujourd'hui, pour detecter tous les cas
d'erreur
Au niveau du seul compilateur, en effet. Mais avec un coup de
main de l'éditeur de liens, je pense qu'on doit pouvoir gérer
beaucoup plus de choses.
ou bien du code supplémentaire (des try/catch en plus) écrit
par le programmeur quand il sait qu'aucune exception n'est
possible. Personnellement, le deuxième ne me gène pas trop
(surtout, que les cas où il serait nécessaire sont bien
rare).
Tout à fait.Mais c'est contraire à la philosophie de C++, et n'était pas
acceptable au comité.
C'est bien triste :=) Je ne comprends pas très bien cette
philosophie,
le choix fait pour les exceptions (ceinture mais pas
bretelles) me paraît incohérent par rapport au choix réalisé
par rapport au mot clé const (ceinture et bretelles).
Pour les exceptions, on se contente de la promesse de celui
qui a écrit le prototype de la fonction, et au pire, s'il ne
la tient pas, on relève vaguement l'erreur lors de
l'exécution, mais on ne s'émeut pas plus que ça de le voir
appeler des sous-fonctions qui n'ont pas pris le même
engagement que lui concernant la liste des exceptions
déclenchées.
Pour le traitement des objets constants, au contraire, si le
programmeur s'engage à ne pas écrire sur un objet, on le
traîne sur le banc d'infâmie s'il a le malheur d'appeler une
sous-fonction qui ne prend pas le même engagement.
Certes, on a mis à sa disposition le const_cast pour
s'auto-amnistier, mais, au moins, on sait que quand on écrit
un const_cast, on prend la responsabilité de ce qui va se
passer. Et je trouve que c'est la meilleure option : on est
libre de faire ce qu'on veut, mais par défaut, on est protégé
contre 90% des situations à problèmes.