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

10 réponses

1 2 3 4
Avatar
Loïc Joly
Manuel Zaccaria wrote on 22/07/2006 23:35:


Des spécifications d'exception comme en java ? J'espère bien que non.

Herb Sutter résume bien bien la situation :



les 2 points sont liés ? Java fait une vérification à la compil.,
l'article ne parle que de vérification au runtime.

ma question portait sur un contrôle à la compilation.


Pour faire ça, il faut à la compilation fournir au compilateur les
données nécessaires pour valider qu'une fonction en cours ne risque pas
d'émettre d'autres exceptions que celle qu'il a déclarées.

Fournir ces données, c'est au choix :

- faire la croix sur tout le code existant, puisque ça veut dire imposer
d'écrire les spécifications d'exception sur chaque déclaration de fonction.

- faire une croix sur la compilation séparée (et donc les biblithèques
livrées sans code source).

Je ne pense pas qu'aucun de ces choix soit acceptable en C++. D'où la
vérification au run-time.

--
Loïc


Avatar
James Kanze
Sylvain wrote:
Manuel Zaccaria wrote on 22/07/2006 23:35:


Des spécifications d'exception comme en java ? J'espère bien
que non.



Herb Sutter résume bien bien la situation :



les 2 points sont liés ? Java fait une vérification à la
compil.,


Ce n'est pas tout à fait vrai. Le Java fait une vérification à
la compile pour certaines exceptions, et pas pour d'autres. Et
c'est impossible en Java d'écrire une garantie qu'une fonction
ne sortira pas par une exception, qui est la seule garantie
réelement utile. (Et ça ne me déplairait pas qu'elle soit
enforcée par le compilateur en C++, mais ça ne va pas se faire.)

--
James Kanze
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
Manuel Zaccaria wrote:
Sylvain a écrit:


Des spécifications d'exception comme en java ? J'espère bien
que non.




Herb Sutter résume bien bien la situation :




les 2 points sont liés ? Java fait une vérification à la
compil., l'article ne parle que de vérification au runtime.



ma question portait sur un contrôle à la compilation.



Si je me souviens bien, le fait de mettre des specifications
d'exception _force_ le compilateur a insérer des vérifications
au runtime (ce qui conduit à un abort() inconditionel si on
les viole).


Il oblige un peu l'équivalent d'un bloc de try autour de la
fonction, oui. Mais le coût d'un bloc de try est zéro sur les
compilateurs que j'utilise d'habitude ; seulement une entrée en
plus dans un tableau et du code qui ne s'exécute que si
l'exception est levée.

--
James Kanze
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
James Kanze wrote on 23/07/2006 02:13:

Ce n'est pas tout à fait vrai. Le Java fait une vérification à
la compile pour certaines exceptions, et pas pour d'autres. Et
c'est impossible en Java d'écrire une garantie qu'une fonction
ne sortira pas par une exception, qui est la seule garantie
réelement utile. (Et ça ne me déplairait pas qu'elle soit
enforcée par le compilateur en C++, mais ça ne va pas se faire.)


peut-être, dont acte, etc, mais ça ne réponds pas à ma simple question:
est-ce que le pré-précesseur (de M$ à tout le moins) va continuer des
années à me balancer des warnings à la compil. pour ne dire qu'il n'a
rien à faire des info fournies ou peut-on imaginer un jour qu'un
contrôle local / hérité soit réalisé pour une méthode _déclarant_ ses
exceptions.

(je ne parle pas ici d'un nécessaire catch global qui existe dèja, ni de
prétendue incompatibilité avec des codes qui ne déclarant rien n'ont
aucune raison de ne plus marcher).

Sylvain.

ps: les throwable vs exception de Java sont un peu hors sujet vis à vis
du comportement normé d'un compilo C++.

Avatar
Sylvain
Loïc Joly wrote on 23/07/2006 00:07:

ma question portait sur un contrôle à la compilation.


Pour faire ça, il faut à la compilation fournir au compilateur les
données nécessaires pour valider qu'une fonction en cours ne risque pas
d'émettre d'autres exceptions que celle qu'il a déclarées.


sans chercher midi à 14h, pour appeler des fonctions dans une méthode on
a déjà fournit au compilo des infos sur des fct (leur proto via des
include) - si ces fct ne déclarent aucun exception (par exemple les libs
courantes) le compilo considerera qu'elles ne lèvent aucune exception
(oui cela peut être faux au runtime, mais ce n'est pas le point et il
faut faire avec l'existant); si certaines fct listent des exception et
si d'autres sont explicitement lévées par le code de la méthode
elle-même, le compilo verifierait qu'elles sont soit catchées localement
soit déclarées dans la liste de throw.

le but serait simplement ici d'assister le codeur qui utilise une
méthode ayant publiée ses exceptions à a) le forwarder en conscience ou
b) penser à implémenter un catch local.


Fournir ces données, c'est au choix :
- faire la croix sur tout le code existant, puisque ça veut dire imposer
d'écrire les spécifications d'exception sur chaque déclaration de fonction.


imho, la fin est sans rapport avec le début.

le code existant fonctionne comme il est, pourquoi le contraire? au pire
l'appel au main est dans un try/catch pour récupérer un beau core dump.

les nouvelles méthodes qui /choississent/ de déclarer leurs exceptions
imposerait en effet à leur utilisateur et supplanteur la même 'corvée',
mais est-ce bien grave ? le cut/paste ne marcherait plus ? ;)

- faire une croix sur la compilation séparée (et donc les biblithèques
livrées sans code source).


je ne vois pas pourquoi non plus, dans la plupart des cas "sans code
source" signifiera avec headers qui donnent les protos complets de fcts.
dans les cas liés à des architectures propriétaires, la norme est déjà
violée et/ou réécrite.

Je ne pense pas qu'aucun de ces choix soit acceptable en C++. D'où la
vérification au run-time.


existe-t-elle ? ou les discussions autour des exceptions portent encore
sur le choix pré-proc. vs runtime ? (l'exemple cité faisant intervenir
une std::unexcepted me parait lié à une lib. (STL) plutôt qu'à une règle
du compilo.).

Sylvain.


Avatar
Sylvain
James Kanze wrote on 23/07/2006 02:13:

Ce n'est pas tout à fait vrai. Le Java fait une vérification à
la compile pour certaines exceptions, et pas pour d'autres. Et


il oblige à catcher ou lister toutes les exceptions devant être
traitées. (la règle entre les "exceptions à gérer" et celles qui peuvent
se balader librement est un autre sujet en soi).

c'est impossible en Java d'écrire une garantie qu'une fonction
ne sortira pas par une exception, qui est la seule garantie
réelement utile.


bien sur que si, le code suivant ne sortira *jamais* par une exception;
y compris si c'est une méthode tournant dans un thread tué à la volée
(une InterruptedException ou un ThreadDeath sera reçu par le catch local).

returnType methodeName {
try {
// whatever
}
catch (Throwable err){
}
}

(Et ça ne me déplairait pas qu'elle soit enforcée par
le compilateur en C++, mais ça ne va pas se faire.)


ah! "ça ne va pas se faire" ... cette année, jamais ? faut écrire son
pré-pré-proc ? (10 ans que j'ai laché ça!...)

Sylvain.

Avatar
Loïc Joly
Loïc Joly wrote on 23/07/2006 00:07:


ma question portait sur un contrôle à la compilation.



Pour faire ça, il faut à la compilation fournir au compilateur les
données nécessaires pour valider qu'une fonction en cours ne risque
pas d'émettre d'autres exceptions que celle qu'il a déclarées.



sans chercher midi à 14h, pour appeler des fonctions dans une méthode on
a déjà fournit au compilo des infos sur des fct (leur proto via des
include) - si ces fct ne déclarent aucun exception (par exemple les libs
courantes) le compilo considerera qu'elles ne lèvent aucune exception
(oui cela peut être faux au runtime, mais ce n'est pas le point et il
faut faire avec l'existant);


Pour moi, c'est justement là tout le point.

si certaines fct listent des exception et
si d'autres sont explicitement lévées par le code de la méthode
elle-même, le compilo verifierait qu'elles sont soit catchées localement
soit déclarées dans la liste de throw.


A quoi ça servirait ? Tu aurais une fonction :

int f() throw (A, B);

Et l'utilisateur saurait en lisant ce prototype que la fonction peut
lancer soit A, soit B, soit n'importe quel autre type d'exception, mais
indirectement. Pas très utile comme spécification.

Avec un tel système partiel, je ne vois rien qui puisse faire partie du
langage, puisque rien ne peut être sur. J'y vois juste une possibilité
pour le compilateur d'émettre un warning s'il voit du code qui lui
semble louche, chose qu'il peut déjà faire actuellement.

le but serait simplement ici d'assister le codeur qui utilise une
méthode ayant publiée ses exceptions à a) le forwarder en conscience ou
b) penser à implémenter un catch local.


Fournir ces données, c'est au choix :
- faire la croix sur tout le code existant, puisque ça veut dire
imposer d'écrire les spécifications d'exception sur chaque déclaration
de fonction.



imho, la fin est sans rapport avec le début.

le code existant fonctionne comme il est, pourquoi le contraire? au pire
l'appel au main est dans un try/catch pour récupérer un beau core dump.


Proposes-tu que les fonctions avec une spécification d'exception n'aient
pas le droit d'appeler de fonction n'en ayant pas ?

Ca pourraît être une proposition acceptable, surtout utile AMA pour les
spécifications nothrow. Il faudrait pour ça patcher la bibliothèque
standard à 2/3 endroits (je pense par exemple à std::swap). Et dans ce
cas, il faudrait surtout que rien n'empêche une fonction sans
spécification d'appeler une fonction avec spécification. Un peu comme
const, en fait.

Là on a un système qui me semble utile et implémentable.


les nouvelles méthodes qui /choississent/ de déclarer leurs exceptions
imposerait en effet à leur utilisateur et supplanteur la même 'corvée',
mais est-ce bien grave ?
le cut/paste ne marcherait plus ? ;)


Il n'a jamais marché. Et dans le code que je fais, où souvent les
exceptions ne sont gérées qu'au niveau le plus haut pour arrêter le
programme avec un code d'erreur, j'aurais aisément 3 à 4 fois plus de
lignes pour spécifier mes exceptions que pour le corps de mes fonctions.
L'intérêt des exceptions est de séparer traitement de l'erreur de sa
localisation. Si on doit écrire des choses pour chaque fonction, je ne
vois plus l'intérêt.


- faire une croix sur la compilation séparée (et donc les biblithèques
livrées sans code source).



je ne vois pas pourquoi non plus, dans la plupart des cas "sans code
source" signifiera avec headers qui donnent les protos complets de fcts.


Mes deux propositions étaient exclusives. Si tu pense que la première
est acceptable, effectivement, la seconde n'est plus nécessaire.

dans les cas liés à des architectures propriétaires, la norme est déjà
violée et/ou réécrite.

Je ne pense pas qu'aucun de ces choix soit acceptable en C++. D'où la
vérification au run-time.



existe-t-elle ?


En théorie, oui. Il y a eu une discussion sur run-time/statique, il y a
longtemps (89/90 d'après le § sur ce sujet dans le D&E), et la
conclusion était que bien qu'attirante au premier abord, la vérification
statique n'était pas réalisable (et qu'elle avait finalement des effets
pervers).

En pratique, on s'est rendu compte que la vérification dynamique n'était
finalement pas hyper utile, et qu'elle avait un coût. Et donc certains
compilateurs qui ne l'implémentaient pas vraiment ont décidé de ne pas
l'implémenter du tout.



--
Loïc



Avatar
James Kanze
Loïc Joly wrote:
Loïc Joly wrote on 23/07/2006 00:07:



Proposes-tu que les fonctions avec une spécification
d'exception n'aient pas le droit d'appeler de fonction n'en
ayant pas ?


Je crois que c'était un des points sensibles. Quand on a ajouté
des exceptions au langage, il y avait déjà beaucoup de code
existant. Qui évidemment n'avait pas de spécifications
d'exceptions. Et on le trouvait inacceptable (chose que je
comprends fort bien) de dire que si une fonction tout en bas de
la chaîne d'appel ajouter une exception, qu'on attrappe tout en
haut de la chaîne, qu'il fallait modifier la déclaration de
toutes les fonctions intermédiaire.

D'une certaine façon, la vérification statique des spécification
des exceptions, c'est un peu comme la correction par rapport au
const : elle ne permet pas de démi-mesures. Elle a un effet
transcendant, et quand on y va, il faut y aller à fond. Or, il y
avait beaucoup moins de code existant quand on a introduit le
const.

Ca pourraît être une proposition acceptable, surtout utile AMA
pour les spécifications nothrow. Il faudrait pour ça patcher
la bibliothèque standard à 2/3 endroits (je pense par exemple
à std::swap).


Et tu relève un deuxième souci : les templates. Qu'est-ce que
tu donnes comme spécification aux constructeurs de std::vector,
par exemple ?

Swap est, en fait, un très bon exemple. Il y a bien de cas où on
compte sur le fait qu'il soit nothrow. Mais il n'est pas nothrow
pour la majeur partie des cas : il n'est nothrow que pour les
types de base et des pointeurs (où tout ce qu'on fait est
nothrow), et dans les cas où l'utilisateur a fait des efforts
particuliers. (Sans surcharge spécial, par exemple, il ne serait
pas nothrow pour std::vector.)

Aussi, il y a beaucoup de cas où il n'a pas besoin d'être
nothrow. Quand c'est std::sort qui l'utilise, par exemple.

Et dans ce cas, il faudrait surtout que rien n'empêche une
fonction sans spécification d'appeler une fonction avec
spécification. Un peu comme const, en fait.


Là on a un système qui me semble utile et implémentable.


Aucun système de vérification statique ne pourrait être
réelement utile tant qu'on ne résoud pas le problème des
templates, je crois.

Il y a aussi des problèmes liés à la dynamique du programme.
std::vector<int>::push_back() est, dans la pratique, nothrow, SI
j'ai fait un reserve adéquat avant. Et je n'ai pas de mal à
imaginer des systèmes qui compte sur ce fait. Quelque chose du
genre :

void
ajouteTroisElements( std::vector< int >& dest ) throw()
{
assert( dest.capacity() >= dest.size() + 3 ) ;
dest.push_back( elem1 ) ;
dest.push_back( elem2 ) ;
dest.push_back( elem3 ) ;
}

Une des techniques d'assurer l'integrité transactionnelle, c'est
bien de pousser toutes les opérations qui peuvent échouer (lever
une exception) vers l'avant, pour pouvoir être nothrow par la
suite.

les nouvelles méthodes qui /choississent/ de déclarer leurs
exceptions imposerait en effet à leur utilisateur et
supplanteur la même 'corvée', mais est-ce bien grave ? le
cut/paste ne marcherait plus ? ;)



Il n'a jamais marché. Et dans le code que je fais, où souvent
les exceptions ne sont gérées qu'au niveau le plus haut pour
arrêter le programme avec un code d'erreur, j'aurais aisément
3 à 4 fois plus de lignes pour spécifier mes exceptions que
pour le corps de mes fonctions. L'intérêt des exceptions est
de séparer traitement de l'erreur de sa localisation. Si on
doit écrire des choses pour chaque fonction, je ne vois plus
l'intérêt.


Tout à fait.

Si on régarde les cas où Java fait une vérification statique, il
s'avère que la plupart du temps, c'est parce qu'il se sert d'une
exception quand un code de rétour conviendrait mieux.

[...]
En théorie, oui. Il y a eu une discussion sur
run-time/statique, il y a longtemps (89/90 d'après le § sur ce
sujet dans le D&E), et la conclusion était que bien
qu'attirante au premier abord, la vérification statique
n'était pas réalisable (et qu'elle avait finalement des effets
pervers).


Je crois que « pas réalisable », c'est peut-être un peu fort,
mais entre le problème du code existant et le problème des
templates, c'est sûr que ça n'allait pas de soi, au point qu'une
expression plus nuancée, du genre « difficilement réalisable
dans la pratique », serait trop faible.

Depuis, on a une bonne pratique des exceptions. On sait à peu
près ce que c'est que le code « exception safe », etc. Et on
peut constater que dans la pratique, le C++ s'en tire mieux que
d'autres langages. Pour diverses raisons, sans doutes certaines
qu'on ne connaissait pas quand la décision a été prise.

En pratique, on s'est rendu compte que la vérification
dynamique n'était finalement pas hyper utile, et qu'elle avait
un coût. Et donc certains compilateurs qui ne l'implémentaient
pas vraiment ont décidé de ne pas l'implémenter du tout.


Je ne vois pas le coût. Dans une bonne implémentation, il y a
peut-être un faible coût en mémoire, mais certainement pas en
temps d'exécution. Et j'en trouve une utilité, de même que je
trouve une utilité à la programmation par contrat en général.
(Mais c'est vrai que dans la pratique, l'utilité se limite à la
garantie que telle ou telle fonction ne sort jamais par une
exception.)

--
James Kanze
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
Sylvain wrote:
James Kanze wrote on 23/07/2006 02:13:


Ce n'est pas tout à fait vrai. Le Java fait une vérification
à la compile pour certaines exceptions, et pas pour d'autres.
Et



il oblige à catcher ou lister toutes les exceptions devant
être traitées. (la règle entre les "exceptions à gérer" et
celles qui peuvent se balader librement est un autre sujet en
soi).


Il oblige en fait à catcher ou à lister toutes les exceptions
qui aurait dû être des codes de retour:-). Et qui le sont en
C++, en général.

En revanche, et ce qui est très important, il ne permet pas de
declarer (ni d'implémenter) une fonction garantie de ne jamais
sortir par une exception. Or, on sait que sans de telles
fonctions, un programme correct par rapport aux exceptions n'est
pas possible.

c'est impossible en Java d'écrire une garantie qu'une
fonction ne sortira pas par une exception, qui est la seule
garantie réelement utile.



bien sur que si, le code suivant ne sortira *jamais* par une
exception; y compris si c'est une méthode tournant dans un
thread tué à la volée (une InterruptedException ou un
ThreadDeath sera reçu par le catch local).


returnType methodeName {
try {
// whatever
}
catch (Throwable err){
}
}


Et entre la fin du bloc de catch et le retour, il y a toujours
une possibilité d'un « VirtualMachineError ». (Un bon exemple
d'une chose qui ne doit jamais être une exception.)

Mais ce n'était pas mon propo. Je disais que tu ne pouvais
jamais écrire cette garantie. La déclaration de ta fonction
methodeName, ci-dessus, ne comporte aucune garantie en ce qui
concerne ni les Error ni les RuntimeException.

(Et ça ne me déplairait pas qu'elle soit enforcée par
le compilateur en C++, mais ça ne va pas se faire.)



ah! "ça ne va pas se faire" ... cette année, jamais ? faut
écrire son pré-pré-proc ? (10 ans que j'ai laché ça!...)


Jamais. C'est trop tard ; ça cassera trop de code existant.

On est arrivé à un point en C++ où on peut ajouter de nouveaux
features, mais on ne peut pas toucher à l'existant.

--
James Kanze
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
James Kanze wrote on 23/07/2006 12:57:

Il oblige en fait à catcher ou à lister toutes les exceptions
qui aurait dû être des codes de retour:-). Et qui le sont en
C++, en général.


tu aurais pu indiquer: les erreurs que /tu/ prefères retourner via un
code de retour.
je te rapelles qu'une méthode retournant une instance nouvellement créé
ne peux le faire via un param variant (ptr&) et donc n'a d'autre choix
que de lever un exception si l'opération ne peut être réalisée.

au delà des conséquences de cette absence de pointeurs, il reste
(heureusement) les choix personnels, prétendre ce qui /devrait être/
présuppose que tout n'a qu'une seule écriture possible, je ne crois pas
cela.

on peux noter également qu'en C++ ces codes retours ne règlent pas tout
loin s'en faut, si j'écris par exemple un streamer d'extraction, je peux
vouloir coder:

Bytes& operator >> (Bytes&, bool&)
throw (ArrayIndexOutOfBoundsException);
Bytes& operator >> (Bytes&, byte&)
throw (ArrayIndexOutOfBoundsException);
etc ...

il est évident que de tels cas (les exemples ne manquerait pas) ne
s'accorde pas avec la stratégie du code de retour.

le choix de la STL et de _CATCH_IO_END qui positionne un indicateur
interne pour continuer son process est une possibilité (à mi chemin) que
je n'affectionne pas vraiment (reste aussi que ma question n'était pas
liée à la STL).

returnType methodeName {
try {
// whatever
}
catch (Throwable err){
}
}


Et entre la fin du bloc de catch et le retour, il y a toujours
une possibilité d'un « VirtualMachineError ». (Un bon exemple
d'une chose qui ne doit jamais être une exception.)


si tu écris ton byte-code à la main peut être, sinon ?...
(par contre la machine peut être éteinte au milieu de la méthode, soit)

Mais ce n'était pas mon propo. Je disais que tu ne pouvais
jamais écrire cette garantie. La déclaration de ta fonction
methodeName, ci-dessus, ne comporte aucune garantie en ce qui
concerne ni les Error ni les RuntimeException.


Error et RuntimeException (et toutes leurs classes filles) étendent
Throwable et seront catchées ici (mais ce n'est pas le propos).

On est arrivé à un point en C++ où on peut ajouter de nouveaux
features, mais on ne peut pas toucher à l'existant.


soit, pourquoi dans ce cas avoir défini 'throw(...)' dans la déclaration
d'une fonction/méthode ?

Sylvain.


1 2 3 4