OVH Cloud OVH Cloud

Comment rendre une classe "iterable"?

36 réponses
Avatar
Michael
Bonsoir à tous,

j'ai les deux classes suivantes:

class VideoDevice
{
private:
std::string name;
public:
std::string & GetName() const { return name; }
};

class VideoDeviceEnumerator
{
public:
void FillList(TBox * box);
void getVideoDevice(const std::string & fooName, VideoDevice * vd);
};

Les deux fonctions de VideoDeviceEnumerator énumèrent en interne tous les
périphériques de capture vidéo disponibles sur le système... Et la fonction
FillList est tout ce qu'il y a de restrictif puisqu'elle ne fonctionne
qu'avec les objets de la VCL de Borland.

Je voudrais donc rendre cette classe identique à un itérateur, pour que
l'utilisateur puisse faire comme avec les conteneurs de la STL...

Un truc du genre:

VideoDeviceEnumerator vdEnum;

for (VideoDeviceEnumerator::constIterator ite = vdEnum.begin(); ite !=
vdEnum.end(); ++ite)
box->AddItem(ite->GetName());

Et pour obtenir un VideoDevice:

VideoDeviceEnumerator vdEnum;

VideoDeviceEnumerator::constIterator ite = std::find_if(vdEnum.begin
(),vdEnum.end(),"nom du périphérique");

Enfin, vous voyez de quoi je parle ;)

Comment je peux faire ça?

J'ai entendu dire que c'était pas conseillé de dériver des classes de la
STL, donc j'ai bien peur de devoir réimplémenter tout ça :(

Merci d'avance...

Mike

6 réponses

1 2 3 4
Avatar
Michel Decima
kanze wrote on 25/07/2006 09:47:

En C++ (§15.4/8,9) :

Pas de distinction entre deux catégories d'exception. Et s'il y
a une exception-specification, c'est véritablement une
garantie : quoi qui arrive, on ne sort pas avec une exception
qui n'est pas citée, et si elle est vide, on ne sort pas de la
fonction avec une exception.


il faut vraiment que je change de compilo alors:

int foo() throw(ExceptionOther){
throw Exception(3);
}

sort avec une 'Exception' ...



Effectivement, il faut que tu changes de compilateur, parce
que le comportement que tu decris n'est pas conforme: A l'execution,
le programme doit appeler std::unexpected_handler, qui par defaut
appelle abort (de toute facon, ce handler ne peut pas retourner
a l'appelant).
C'est le comportement que j'observe avec les deux compilateurs
que l'ai sous la main (xlC-6 et g++-4.1), sans options de compilation
particulieres.

Je laisse le plaisir de la citation du bon paragraphe de la norme
a ceux qui en on une copie sous la main.


Avatar
kanze
Sylvain wrote:
kanze wrote on 25/07/2006 09:47:

Je parlais de ce qu'on appelle les « out parameters » en
anglais.


ok donc des "paramètres par référence", à moins que tu
n'inclues implicitement également le mot-clé "out" jouant sur
les régles de modifications du paramètre formel (ou pire les
modifiers de .net).


Je parlais d'un concepte, non d'une technique d'implémentation.
On utilise les références pour l'émuler en C++, mais ce n'est
qu'un à peu près ; un paramètre out doit logiquement se
comporter exactement comme une valeur de rétour.

Java ne supporte ni out, ni inout.


il supporte 'final' qui agit comme 'const' pour un "in
parameter".


Tous les paramètres Java sont des paramètres in. D'où la
nécessité de tous ces Holder dans le binding IDL de Java. (Note
que dans le binding C++ non-plus, un paramètre out n'est pas une
référence à l'objet, mais une référence à un pointeur.)

(Le C++, en fait, ne supporte pas réelement out non plus.
Mais on arrive aux mêmes fins avec des inout la plupart du
temps.)


match nul avec une référence Java - sauf pour changer la
valeur d'un pointeur (un "out type [const] * &").

C'est un concepte assez connu, il me semble.


archi connu ! mais comme je n'ai vu aucun lien avec le point
discuté...


Parce qu'archi connu ou non, tu ne le comprends pas.

À titre d'exemple, essayer d'écrire l'équivalant en Java de
std::swap.


swaper quoi appartenant à qui ??


Swapper deux valeurs. Comme fait std::swap en C++. Quelque chose
qui pourrait utiliser une fonction sort, par exemple -- si j'ai
un tableau A[n], échanger les valeurs A[i] et A[j], par exemple.

si c'est une classe clonable je ne vois pas le problème, si
c'est des types primitifs c'est également facile, si par
contre ce sont des pointeurs ... je l'ai déjà dit.


Je ne vois pas ce que Clonable a à faire là-dedans. On a deux
variables (ou deux éléments d'un tableau), ça revient au même).
On veut les échanger. Je ne vois pas ce qu'il y a de si
compliqué à comprendre là-dedans.

ceci étant, les map et autres ensembles triés existent (c'est
là que le "appartenant à qui" intervient), il sera donc
toujours possible de swap dans un contexte donné.


C'est toujours possible d'écrire le code pour effectuer le swap
en ligne, oui. Personne n'a dit le contraire. En revanche, c'est
impossible à écrire une fonction générique pour le faire.

En ce qui concerne swap même, à la rigueur, ce n'est pas trop
génant ; la fonction est assez simple que l'écrire en ligne va
encore, même si ça manque d'élégance. Mais swap est
représentatif de tout un ensemble de fonctions qui ont besoin
des paramètres inout ou out.

Je n'arrive pas à comprendre ce que tu ne comprends pas. Tu
sais très bien que le Java ne t'oblige à lister que
certaines exceptions, non toutes. C'est là le problème,
parce que la garantie la plus intéressante, c'est bien qu'il
n'y aura pas d'exception. Du tout. Qu'elle hérite de
RuntimeException ou de Error, ou non. C'est dans la pratique
peut-être la seule garantie un peu près utile.


je suis désolé mais justement la classe de l'exception à son
importance (tel que Java se pratique en tout cas). les erreurs
RuntimeException et Error sont réputés non récupérables - un
peu moins pour les filles de RuntimeException, mais la plupart
résulterait d'erreur ou de manque grossier dans le codage
(aucune méthode standard ne lève de telle exception par
surprise).


(Quand je parle de RuntimeException ou de Error, j'entends
évidemment les classes dérivées.)

D'abord, il y a bien des cas où elles sont récupérables ;
beaucoup d'applications de C++ récupèrent des manques de
mémoire (OutOfMemory, en Java, qui dérive de Error). La
distinction n'est pas vraiment claire et nette. Et si une erreur
n'est pas récupérable, pourquoi lever une exception ?

Le fait reste que je sors de la fonction par une exception, et
il n'y a rien que je peux dire ni faire au niveau de la
spécification formelle de la fonction pour garantir que ça ne se
produit pas. Il n'y a que la documentation, et si le code ne s'y
conforme pas, tant pis -- je n'ai aucun moyen simple non plus
d'y ajouter des vérifications du contrat.

Idéalement, on l'aurait, et elle serait vérifiée par le
compilateur. En C++, on l'a, mais avec vérification lors de
l'exécution -- c'est loin d'être idéal, mais c'est mieux que
rien.


tu peux m'expliquer ce point ?

le code:

class Exception {
public:
Exception(int) {}
};

int foo() throw()
{
int x = 3, y = 0;
int z = x / y;
int* p = NULL;
*p = 1;
throw Exception(3);
}

a, il me semble, 3 raisons de partir en exception; si je
catche l'appel à foo par un handler générique ("...") je tombe
pour chacune d'elle (en commentant la précédante) dans mon
handler.


Dans quel langage ? Certainement pas avec le C++.

Les deux premières erreurs sont des comportements indéfinis. En
dehors des processeurs embarqués, je m'y attendrais à un core
dump (ou son équivalent sous Windows) ; c'est aussi ce que j'ai
avec tous les compilateurs que je connais, que ce soit sous
Linux (g++), sous Solaris (g++, Sun CC), ou sous Windows NT
(VC++, g++).

En ce qui concerne la troisième, le langage est clair. Il faut
que la fonction unexpected() soit appelée, qui, par défaut
appelle terminate(), qui par défaut appelle abort(). Et là
aussi, les compilateurs auxquels j'ai accès sont tous
conforment.

que signifie alors la "vérification à l'exécution" ?


Que tu as un traitement spécial dans ce cas-ci. Bien défini par
la norme, et qui ne permet pas la continuation du programme.

il faut que je change de compilo ?


S'il s'agit d'un compilateur pour un système non-embarqué, il en
a l'air. Ou au moins utiliser les bonnes options ; je ne
connais aucun compilateur qui essaie même d'être réelement
conforme sans des options particulières (« -std=c++98
-pedantic » pour g++, « /EHs /GR » pour VC++, etc.)

En l'occurance, VC++ (une fois de plus !) fait encore mieux que
les compilateurs Unix, parce qu'il fait aussi une analyse
statique, et émet un avertissement lors de la compilation :
« function assumed not to throw an exception but does ».

C'est à croire que tu connais ni le Java, ni le C++, parce
que tu l'a exactement à l'envers. La déclaration ci-dessus
en Java garantie seulement que la fonction ne peut pas lever
une parmi un petit sous-ensemble des exceptions. Et j'ai
déjà (souvent d'ailleurs) vu des fonctions Java avec une
déclaration pareille sortir par une exception.


une exception que tu n'as pas à déclarer car dans 99% des cas
cela ne sert à rien (VM morte, stack corrompu, etc ...) - dit
que cet indéterminisme te parait inacceptable (le cas échéant)
mais pas que ce n'est pas une garantie (ou accepte que
"garantie" ait un sens propre au langage).


D'accord. C'est une garantie sauf quand ce n'en est pas une.

Ce n'est pas très utile si on veut écrire du code correct.

Quand tu spécifies throw() sur une fonction en C++, en
revanche, tu es 100% sûr que la fonction ne sortira jamais
par une exception.


ben j'attends ta réponse sur l'exemple précédent, parce que
l'expérience me montre le contraire pour l'instant.


Alors, tu n'as pas d'expérience avec un compilateur C++. Parce
que mon expérience, avec trois compilateurs différents, sur
trois systèmes différents, montre que les compilateurs sont tous
conforme. Au moins dans leurs versions récentes : on ne peut
pas s'attendre à ce que les compilateurs apparus avant la norme
(1999), genre Sun CC 4.2 ou VC++ 6.0 traite la spécification
d'exception correctement, vue la volatilité qu'elle a eu pendant
la normalisation. Et la génération des core dumps se répose sur
le support qu'en donne le système et le hardware : on ne
l'avait pas sous MS-DOS, par exemple.

La seule différence entre les deux, c'est qu'il faut en
citer les unes dans la déclaration de la fonction, mais pas
les autres. C'est vraiement la seule différence, selon le
langage. Et si tu régardes les cas réels, dans la API de
Java, tu constateras que les seules qu'il faut lister, ce
sont celles qu'il faut typiquement traiter à un niveau tout
près. C-à-d celles où on se servira d'un code de rétour
d'une fonction en C++, parce que c'est quand même moins
lourd.


je suis assez d'accord; mais la lourdeur ne sera présente que
si on fait exprès d'être lourd: on peut (et il est pertinent
de) faire un test avant l'opération et sortir directement en
erreur ou on peut y aller bourrin et jouer du try/catch, e.g.


Je ne dis pas que des solutions n'existent pas. Je ne dis même
pas que c'est un problème absolument grave. Simplement que Java
ne tient pas vraiment ses promesses ici. (Et en étant honnête,
les promesses sont celles du marketing Java chez Sun. Et qui est
assez naïf pour croire aux promesses du marketing ?) Le choix
de Java, dans ce cas-ci, n'est pas aussi heureux que celui du
C++. Ça ne veut pas dire que c'est inacceptable, ou
inutilisable.

int foo(Object definedOrNot){
if (definedOrNot == null)
return -1;
return definedOrNot.g();
}

ou

int foo(Object definedOrNot){
try {
return definedOrNot.g();
}
catch (NullPointerException npe){
return -1;
}
}

les 2 sont valides, la seconde est inutilement lourde; reste
que la 'proximité' utilisée ici n'est pas toujours vérifiée;
le codeur doit faire preuve de discernement.


Tu lèves un point intéressant ici. Pour des cas d'erreur, quand
c'est raisonablement faisable, c'est prèsque toujours mieux
d'offire l'utilisateur la possibilité de vérifier d'abord, et de
traiter l'erreur dans la fonction « active » comme une
violation du contrat. Les avantages quant à l'integrité
transactionnelle sont énormes. Et ceci, quelque soit la
méchanisme choisi pour rapporter l'erreur.

Pratiquement, ce n'est pas toujours possible. Tester pour un
pointeur nul, c'est simple ; savoir si un new va réussir, en
revanche... D'autant plus qu'entre le moment que tu fais le
test, et le moment que tu alloues effectivement, un autre thread
aurait pû s'intercaler et prendre la mémoire.

Et évidemment, en cas de violation du contrat, il faut en
général aborter, et non essayer de continuer.

L'exemple typique où une exception est la solution préférée,
c'est std::bad_alloc (en C++) ou java.lang.OutOfMemoryError
(en Java). C'est un cas type où l'erreur ne pourrait pas
être traitée localement.


"le codeur doit faire preuve de discernement" ...

s'il s'agit d'une initialisation de buffer de transaction, je
ferais un traitement local pour essayer de poursuivre avec un
tampon plus petit plutôt que de jeter l'éponge.


C'est pour ça qu'il y a « new(nothrow) » en C++.

Si tu écris un serveur, selon le protocol, tu risques le cas où
la quantité de mémoire utilisée dépend de la complexité de la
requête. Renvoyer une réponse du genre : « requête trop
complexe » est normalement acceptable (et même prévu par le
protocol) dans ces cas-ci. Crasher le serveur ne l'est pas.
Trapper std::bad_alloc (ou java.lang.OutOfMemory) en haut de la
gestion de la requête est une bonne solution. Mais pour assurer
l'integrité transactionnelle, il faut bien que tu puisses
effectuer certaines opérations sans risque qu'une autre
exception vient t'interrompre. Chose qui est beaucoup plus
difficile à garantir en Java qu'en C++.

Il y en a beaucoup de ce genre qui dérivent de
RuntimeException ; ce sont des erreurs que, si on veut les
traiter, il faut les traiter à un niveau assez élevé,


je dirais plutôt cela des Error.


Tel que je l'avais compris, j'aurais cru que normalement Error,
c'est les exceptions que tu ne dois jamais attraper (et qui,
AMHA, ne doit pas être des exceptions, mais des avorts),
RuntimeException celles que tu dois attraper au plus haut
niveau, et les autres, celles qu'on peut souvent traiter
localement (et qui serait mieux comme code de retour). Mais
j'avoue que les exceptions qu'on trouver réelement dans chaque
hièrarchie n'y correspondent pas, et qu'on fait, il me semble
qu'il y a beaucoup d'arbitraire : j'ai du mal à voir
OutOfMemory comme une VirtualMachineError (et donc une Error),
par exemple.

Pratiquement, à part la classification de certaines exceptions,
le comportement de Java me paraît un bon défaut. Il y a bien peu
de fonctions où on pourrait dire qu'elle ne lève ni Error ni
RuntimeException, et ça serait vraiment fastidieux s'il fallait
les préciser partout. En revanche, pour les quelques fonctions
où on a besoin de garantir aucune exceptions, sans exception,
c'est vraiment dommage que la possibilité de le spécifier
n'existe pas.

Et évidemment, si tu prends la position qu'il existe des codes
de retour pour des erreurs à traiter localement, c-à-d celles
qui ne dérive ni de Error ni de RuntimeException, le défaut de
Java devient exactement celui de C++ : que dans l'absence d'une
spécification d'exception, toutes les exceptions sont permises.
Seulement qu'en C++, je peux limiter encore plus, non en Java.

[...]
En C++ (§15.4/8,9) :

Pas de distinction entre deux catégories d'exception. Et
s'il y a une exception-specification, c'est véritablement
une garantie : quoi qui arrive, on ne sort pas avec une
exception qui n'est pas citée, et si elle est vide, on ne
sort pas de la fonction avec une exception.


il faut vraiment que je change de compilo alors:

int foo() throw(ExceptionOther){
throw Exception(3);
}

sort avec une 'Exception' ...


En effet, il faudra que tu changes de compilateur. Je serais
même curieux ce que tu as comme compilateur. Si c'est quelque
chose d'ancien, je peux comprendre, et je te conseillerais fort
une mise à jour (mais je sais que ce n'est pas toujours
faisable, pour de nombreux raisons). Mais si tu as ce
comportement avec un compilateur récent, ça m'inquiète, et
j'aimerais savoir lequel, pour pouvoir l'éviter.

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


Avatar
Sylvain
kanze wrote on 26/07/2006 11:42:

Je parlais d'un concepte, non d'une technique d'implémentation.
...
Parce qu'archi connu ou non, tu ne le comprends pas.


arg, pas taper! j'avais compris concept aussi, n'empêche pas d'illustrer.

À titre d'exemple, essayer d'écrire l'équivalant en Java de
std::swap.
...


On veut les échanger. Je ne vois pas ce qu'il y a de si
compliqué à comprendre là-dedans.


j'ai pas dit compliqué, j'ai dit ça dépends du type !
pour swapper 2 int, on le fera localement en récrivant à chaque fois la
function idoine, on n'utilisera pas un unique template soit, mais comme
tu le dis "c'est pas trop génant", la philosophie (les obligeations
plutôt) de Java n'est pas aux templates c'est tout (hormi pour un
vecteur ou une liste).

(Quand je parle de RuntimeException ou de Error, j'entends
évidemment les classes dérivées.)


on est d'accord.

D'abord, il y a bien des cas où elles sont récupérables ;
beaucoup d'applications de C++ récupèrent des manques de
mémoire (OutOfMemory, en Java, qui dérive de Error).


la plupart des RuntimeException sont récupérables si elles sont
injustifiées, pas dans les cas exceptionnel (au sens propre).

une bête méthode comme System.arrayCopy peut lever à peu près toutes les
RuntimeException de java.lang mais cela si le code est en mode
"bourrin", ie ne teste rien.

de mon expérience je dirais qu'une appli (la partie code à soi)
n'utilise jamais de RuntimeException (car on sait et/ou vérifie ce que
l'on fait) alors qu'une librairie les utilisera abondamment,
principalement parce qu'elle ne maitrise pas la validité des paramètres
reçues et parce que l'usage ('un certain usage') veut que l'on signale
ces erreurs par un exception bien nommée; a contrario, en C++ on
utilisera volontiers des codes d'erreurs car une partie est plus au
moins courante (j'ai pas dit "standard") par exemple errno.h

la plupart des Error sont vraiment non récupérables; sur du Java
embarqué on traitera bien sur OutOfMemory; sur un PéCé la VM est censé
faire du ménage (garbager) si besoin, utiliser un tas dynamique, de
sorte que si elle ne peut pas allouer, c'est qu'il est vraiment
impossible de (dans le cas le plus courant s'entends, un code
particulier peut vouloir une règle opposé et être capable de s'adapter).

La distinction n'est pas vraiment claire et nette. Et si
une erreur n'est pas récupérable, pourquoi lever une exception ?


pour essayer quand même de sauver ce qui peut l'être ? parce que la
règle de parfois abuser des throw ? ...

Le fait reste que je sors de la fonction par une exception, et
il n'y a rien que je peux dire ni faire au niveau de la
*spécification formelle* de la fonction pour garantir que ça ne
se produit pas. Il n'y a que la documentation, et si le code ne
s'y conforme pas, tant pis


soit.

je n'ai aucun moyen simple non plus
d'y ajouter des vérifications du contrat.


ça dépends ce que tu entends par ajouter; si tu as des doutes sur le
respect du contrat tu peux surrounder par un try/catch.

int foo() throw()
{
throw Exception(3);
}


Les deux premières erreurs sont des comportements indéfinis.


ok.

En ce qui concerne la troisième, le langage est clair. Il faut
que la fonction unexpected() soit appelée, qui, par défaut
appelle terminate(), qui par défaut appelle abort(). Et là
aussi, les compilateurs auxquels j'ai accès sont tous
conforment.


vc8 (Studio2005, cl 14.0.50727.42) est conforme, vc6 (Studio98, cl
12.0.8168.0) ne l'est pas.

[...] « /EHs /GR » pour VC++


ce sont les réglages pour les 2: /GR pour les infos RTTI et /GX (ou
/EH[sc]) pour les exceptions C++.

VC++ [..] émet un avertissement lors de la compilation :
« function assumed not to throw an exception but does ».


quelle version (de cl) ?

Alors, tu n'as pas d'expérience avec un compilateur C++.
[...] VC++ 6.0 traite la spécification d'exception
correctement, vue la volatilité qu'elle a eu pendant
la normalisation. Et la génération des core dumps [...]


si j'ai un peu d'expérience des compilo VC (trop même prenant ces
extensions pour un standard); il traite incorrectement et ignore
passablement ce qu'est un core dump (notion très linux).

Et qui est assez naïf pour croire aux promesses du marketing ?


:-)) (j'en profite, c'est rare dans nos fils...)

Il y en a beaucoup de ce genre qui dérivent de
RuntimeException ; ce sont des erreurs que, si on veut les
traiter, il faut les traiter à un niveau assez élevé,


je dirais plutôt cela des Error.


Tel que je l'avais compris, j'aurais cru que normalement Error,
c'est les exceptions que tu ne dois jamais attraper (et qui,
AMHA, ne doit pas être des exceptions, mais des avorts),


oui, mais comme abort n'étant pas la règle (sauf si la VM complète
explose), tu peux vouloir traiter les Error très haut pour choisir entre
continuer (peut être en ayant perdu tous les docs, fichiers, etc) ou en
effet aborter l'appli.

RuntimeException celles que tu dois attraper au plus haut
niveau, et les autres, celles qu'on peut souvent traiter
localement (et qui serait mieux comme code de retour).


oui aussi avec une nuance pour les RuntimeException qui résulte d'étapes
intermédiaires (pas "locale -premier appel de méthode- mais pas non plus
trop profond) en fait ça dépend surtout du type d'appli (si l'action
résulte d'un event utilisateur ou traitera plus près que pour un working
thread d'un serveur faceless).

j'avoue que les exceptions qu'on trouver réelement dans chaque
hièrarchie n'y correspondent pas, et qu'on fait, il me semble
qu'il y a beaucoup d'arbitraire :


oui encore.

j'ai du mal à voir OutOfMemory comme une
VirtualMachineError (et donc une Error), par exemple.


je le disais plus haut, c'est parce que ""idéalement"" il y a tjrs de la
mémoire (virtuelle) dispo. ce "OutOfMemory" là ne correspond pas aux
limites tiny, small, huge du C d'antan (en imaginant que ce n'étaient
pas que des modèles mémoires du monde Intel).

les préciser partout. En revanche, pour les quelques fonctions
où on a besoin de garantir aucune exceptions, sans exception,
c'est vraiment dommage que la possibilité de le spécifier
n'existe pas.


je suis d'accord, c'est dans cet esprit que j'indiquais (là haut dans le
fil) que l'on peut toujours mettre un catch all pour pallier cette
violation (... qui selon moi viendra d'un défaut de codage plus que d'un
vrai manque de rigueur de Java - tu l'as dit, on ne voudrais pas
déclarer des throw(liste exhaustive) partout).

il faut vraiment que je change de compilo alors:
int foo() throw(ExceptionOther){
throw Exception(3);
}
sort avec une 'Exception' ...


En effet, il faudra que tu changes de compilateur. Je serais
même curieux ce que tu as comme compilateur.


ici cl 12 comme 14 (Studio 98 et 2005 (professional edition)) ont le
même comportement et throw bien une 'Exception' catchable.

c'était mon point initial! ces compilos n'utilisent pas du tout les
exceptions présentes dans la déclaration - vc2005 a "seulement" ajouter
la gestion correcte du 'nothrow'.

Sylvain.



Avatar
Alain Gaillard


VC++ [..] émet un avertissement lors de la compilation :
« function assumed not to throw an exception but does ».



quelle version (de cl) ?


Et bien vc8 (Studio2005, cl 14.0.50727.42), c'est à dire le tien émet
bien cet avertissement.

ici cl 12 comme 14 (Studio 98 et 2005 (professional edition)) ont le
même comportement et throw bien une 'Exception' catchable.


vc8 (Studio2005, cl 14.0.50727.42) se comporte effectivement comme tu le
dis. On dirait que Krosoft a (à tort cela s'entend) interprété 15.4,11 à
sa convenance :-) et donc il n'est pas conforme puisque 15.4,9 n'est pas
respecté.


--
Alain


Avatar
kanze
Sylvain wrote:
kanze wrote on 26/07/2006 11:42:

À titre d'exemple, essayer d'écrire l'équivalant en Java de
std::swap.
...


On veut les échanger. Je ne vois pas ce qu'il y a de si
compliqué à comprendre là-dedans.


j'ai pas dit compliqué, j'ai dit ça dépends du type !

pour swapper 2 int, on le fera localement en récrivant à chaque fois la
function idoine, on n'utilisera pas un unique template soit, mais comme
tu le dis "c'est pas trop génant", la philosophie (les obligeations
plutôt) de Java n'est pas aux templates c'est tout (hormi pour un
vecteur ou une liste).


Certainement. En ce qui concerne swap. Je l'ai pris comme
représentante d'une classe de fonctions où des paramètres inout
sont utiles. Chaque fois qu'une fonction doit modifier deux
objets de façon synchrone. La plupart du temps en Java, ce n'est
pas un gros problème, parce que ce qu'on passe par valeur, c'est
la variable, et la variable, en Java, comporte toujours un
niveau d'indirection supplémentaire. Mais il peut bien y avoir
des cas où on veut modifier la variable même, et non l'objet
qu'elle désigne : le cas des paramètres out de Corba est un bon
exemple.

[...]
de mon expérience je dirais qu'une appli (la partie code à
soi) n'utilise jamais de RuntimeException (car on sait et/ou
vérifie ce que l'on fait)


Ce qui m'inquiète un peu, c'est que j'ai déjà vu des articles
sur Java qui conseillaient de dériver toutes les exceptions de
l'application de RuntimeException, afin de ne pas avoir à
s'emmerder avec les spécifications d'exception. Dans la
pratique, si tu ne vas pas traiter l'exception tout de suite, et
que tu t'attends à ce qu'elle propage à travers 90% des
fonctions de ton application, il faut avouer qu'avoir à la
déclarer partout est un peu embêtant.

Je ne vois pas de vrai solution. Celle de C++ est loin d'être
parfait, mais je ne vois pas en quoi celle de Java est mieux --
ses problèmes sont différents, mais au moins aussi graves.

[...]
La distinction n'est pas vraiment claire et nette. Et si une
erreur n'est pas récupérable, pourquoi lever une exception ?


pour essayer quand même de sauver ce qui peut l'être ? parce
que la règle de parfois abuser des throw ? ...


La question a été largement discutée dans les groupes C++
anglophone. Je ne crois pas qu'il y ait un vrai consensus, mais
un bon nombre d'experts considèrent que dans des cas où on a
réelement une erreur grave, et qu'on n'est pas sûr de l'état du
programme ou de la cohérence interne de ses données, il faut
sortir avec une maximum de préjudice (pour que les systèmes
autour sachent qu'on a un problème) et en faisant le moins
possible. Ou le moins possible signifie : pas de destructeurs
(en C++), et pas de clause finally (en Java). Parce que si on ne
sait pas vraiment l'état du programme, on ne sait pas vraiment
ce qu'il va faire dans ce code, et on n'accepte pas la risque
d'empirer la situation.

C'est vrai que l'argument est plus pertinant en C++ qu'en
Java : d'une part, les clause finally sont bien moins
fréquentes en Java que les destructeurs en C++, et de l'autre,
l'isolation complète entre le programme utilisateur et la
gestion des ressources de bas niveau (par exemple la mémoire)
fait que les chances d'une véritable désastre sont moins.

(J'ai essayé de le formuler de façon abstraite, mais je ne sais
pas si c'est clair. L'idée de base, c'est que si je détecte,
disons, une erreur d'indice d'un tableau en C++, il y a bien des
chances que ce ne soit pas l'appelant où se trouve l'erreur,
mais plutôt quelqu'un ailleurs qui a écrit dans la mémoire où il
n'aurait pas dû. Et qu'à partir de là, on ne sait plus où il a
pû écrire ailleur, et donc, on ne sait pas le vrai état d'aucun
objet en mémoire. Tandis qu'en Java, c'est prèsque certain que
l'erreur vient de l'appelant.)

je n'ai aucun moyen simple non plus
d'y ajouter des vérifications du contrat.


ça dépends ce que tu entends par ajouter; si tu as des doutes
sur le respect du contrat tu peux surrounder par un try/catch.


Idéalement, c'est une vérification systèmatique dans la fonction
même (dans la classe de base, même si elle est abstraite, parce
que c'est elle qui définit le contrat).

C'est une idéale ; dans la pratique, évidemment, on ne peut pas
vérifier tout de cette façon.

[...]
En ce qui concerne la troisième, le langage est clair. Il
faut que la fonction unexpected() soit appelée, qui, par
défaut appelle terminate(), qui par défaut appelle abort().
Et là aussi, les compilateurs auxquels j'ai accès sont tous
conforment.


vc8 (Studio2005, cl 14.0.50727.42) est conforme, vc6 (Studio98, cl
12.0.8168.0) ne l'est pas.


Je n'ai que le Studio 2005.

Dans la pratique, je dirais que VC6 était un bon compilateur
dans son temps, mais que son temps a passé. Je ne l'utiliserais
pas dans de nouveaux développements, et j'essaierais de porter
des anciens sur une version plus moderne si j'en avais
l'occasion.

En ce qui concerne la conformité, VC6 est apparu avant la
norme ; c'est donc compréhensible qu'il ne soit pas conforme en
tout. Aussi, l'attitude de MS vis-à-vis de la norme était
beaucoup plus cavalière alors (« la norme, c'est moi », ou
quelque chose du genre). La situation à cet égard s'est beaucoup
amélioré.

[...] « /EHs /GR » pour VC++


ce sont les réglages pour les 2: /GR pour les infos RTTI et
/GX (ou /EH[sc]) pour les exceptions C++.


Je sais. La documentation VC++ dit bien que sans /GR, on n'a pas
la RTTI (et sans la RTTI, on n'est pas conforme), et que c'est
bien /EHs pour spécifier des exceptions C++, mais non la
conversion des trappes système (genre division par 0) en
exceptions. Ce qui donne ce que je veux : des core dump dans le
cas des divisions par 0 ou les déréférences d'un pointeur null,
et des exceptions C++ quand j'exécute throw.

Je sais qu'on peut aussi convertir les trappes système en
exceptions qu'on peut catcher en C++ ; je crois que c'est
l'option /EHa. Je peux imaginer qu'il y a des applications où ça
a un sens, mais ça ne correspond pas à ce qu'il me faut, ni à
quelque chose qu'il m'a jamais fallu.

VC++ [..] émet un avertissement lors de la compilation :
« function assumed not to throw an exception but does ».


quelle version (de cl) ?


14.00.50727.42. (C'est la version qu'on peut télécharger
gratuitement comme version 2005.)

J'avoue que j'étais étonné. De façon agréable.

Alors, tu n'as pas d'expérience avec un compilateur C++.
[...] VC++ 6.0 traite la spécification d'exception
correctement, vue la volatilité qu'elle a eu pendant la
normalisation. Et la génération des core dumps [...]


si j'ai un peu d'expérience des compilo VC (trop même prenant
ces extensions pour un standard); il traite incorrectement et
ignore passablement ce qu'est un core dump (notion très
linux).


L'expression « core dump » vient bien du milieu Unix (non
Linux, mais bien avant) je crois. La chose, en revanche, est
courant sur la plupart des systèmes. Le monde Windows est un peu
particulier, en ce qu'il semble s'attendre à ce que soit aucun
développeur n'ait accès à la machine, soit qu'il y a un
débogueur installé et que l'expert est devant la machine. Ce qui
est sans doute vrai dans le cas des logiciels pré-emballés, mais
ne correspond pas à l'utilisation des PC ici, par exemple.

À cet égard, Linux s'approche plus à Windows qu'à Unix. Par
défaut, il n'y a pas de fichier core (mais on peut l'activer).
Tout en gardant le vocabulaire d'Unix : on continue à dire que
le programme a fait un core dump, même s'il n'y a pas de fichier
core.

Mais quand je parle de « core dump » dans ce contexte-ci, tout
ce que je voulais dire en fait, c'est que le programme a été
terminé par le SE, avec un message d'erreur.

[...]
Tel que je l'avais compris, j'aurais cru que normalement Error,
c'est les exceptions que tu ne dois jamais attraper (et qui,
AMHA, ne doit pas être des exceptions, mais des avorts),


oui, mais comme abort n'étant pas la règle (sauf si la VM
complète explose), tu peux vouloir traiter les Error très haut
pour choisir entre continuer (peut être en ayant perdu tous
les docs, fichiers, etc) ou en effet aborter l'appli.


C'est un choix de Java avec lequel je ne suis pas d'accord.
Parfois, la meilleur chose qu'on peut faire, c'est d'avorter.

[...]
j'ai du mal à voir OutOfMemory comme une
VirtualMachineError (et donc une Error), par exemple.


je le disais plus haut, c'est parce que ""idéalement"" il y a
tjrs de la mémoire (virtuelle) dispo. ce "OutOfMemory" là ne
correspond pas aux limites tiny, small, huge du C d'antan (en
imaginant que ce n'étaient pas que des modèles mémoires du
monde Intel).


Tout dépend de l'application. Dans mon application actuelle,
j'ai installé un new_handler qui termine l'application ; je
sais combien de mémoire elle doit utiliser, elle tourne sur une
machine dédiée, avec suffisamment de mémoire, et la seule raison
possible qu'on n'en a pas assez, c'est qu'on fuit. Mais ce n'est
pas le cas de toutes les applications. Une requête LDAP permet
des expressions arbitraires, et avec assez de niveau
d'embrication, on pourrait épuiser n'importe combien de mémoire.

[...]
il faut vraiment que je change de compilo alors:
int foo() throw(ExceptionOther){
throw Exception(3);
}
sort avec une 'Exception' ...


En effet, il faudra que tu changes de compilateur. Je serais
même curieux ce que tu as comme compilateur.


ici cl 12 comme 14 (Studio 98 et 2005 (professional edition))
ont le même comportement et throw bien une 'Exception'
catchable.


En effet. J'avais oublié le try/catch dans mon programme
d'essai -- sous Solaris (que ce soit Sun CC ou g++), ça ne fait
pas de différence ; je ne l'ai donc pas rémarqué. C'est une
manque de conformité dans VC++. (Même avec le try/catch, j'ai un
« Abort (core dumped) » sous Solaris, aussi bien avec g++
qu'avec Sun CC. Sans exécuter le bloc de catch.)

c'était mon point initial! ces compilos n'utilisent pas du
tout les exceptions présentes dans la déclaration - vc2005 a
"seulement" ajouter la gestion correcte du 'nothrow'.


Un coup d'oeil au code généré de Microsoft montre qu'un
try/catch bloc n'est pas gratuit (en temps d'exécution), même si
on ne lève jamais d'exception. (Il l'est avec g++ ou avec Sun
CC.) Or, l'implémentation des spécifications de throw, c'est de
mettre toute la fonction dans un bloc try/catch implicit. Je
suppose que les gens chez Microsoft ont supposé que c'est un
coût trop élevé pour quelque chose qui ne doit pas servir (parce
qu'évidemment, si j'écris throw(), c'est parce que la fonction
ne va pas sortir par une exception). Je suis quand même étonné
qu'il n'y a pas d'option pour l'activer. Techniquement, c'est
très simple à implémenter, et VC++ a des options pour faire
beaucoup d'autres vérifications, certaines bien plus chères en
termes de temps d'exécution.

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




Avatar
Pierre Barbier de Reuille
Juste au cas où, il y a là trois très bon articles concernants les
itérateurs de la STL:

http://www.ddj.com/TechSearch/searchResults.jhtml?queryText=The+Standard+Librarian%3A+Defining+Iterators+and+Const+Iterators&xE&y=9

Pierre
1 2 3 4