Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

Genre de IEnumerable en C++ ?

15 réponses
Avatar
Mic Mon
Bonjour,

J'aimerais mettre en place une interface =E0-la IEnumerable<T>. En 2
mots, c'est une classe C# de base pour toutes les collections
enumerables, qui permet de traiter de fa=E7on uniforme list et vector
par exemple, lorsqu'on a des boucles du types "foreach ()".

J'aimerais faire la m=EAme chose, ie. j'ai une interface "Interface" que
j'aimerais voir avec 2 m=E9thodes abstraites : "AddChild (Children*)" et
"getChildren (Children*)". Mais que donner comme type de retour =E0
"getChildren" ?

Le probl=E8me est que j'aimerais que les utilisateurs de l'interface ne
voient pas si c'est une std::list ou std::vector ou autre, qu'ils
aient juste le moyen de faire quelque chose du type for_each dedans
(genre BOOST_FOREACH).

Vu que c'est une m=E9thode virtuelle =E0 impl=E9menter dans les classes
d'impl=E9mentation, je ne peux pas en faire une m=E9thode avec template,
donc les solutions =E0 base de "renvoyer une paire d'iterator begin/end"
ou du boost:range me semblent =E9chouer (oblig=E9 de dire si l'iterator
vient de list ou vector ou autre).

Il y aurait la possibilit=E9 de passer en param=E8tre template =E0 cette
interface le container utilis=E9, mais =E7a a le side effect de changer le
type, donc je ne pourrai pas stocker des impl=E9mentations avec
diff=E9rents containers dans un m=EAme vector par exemple. En plus =E7a
couple trop.

La seule chose que je vois est de faire un Adaptor pour iterator,
genre IIterator<T> qui forwarde les appels, puis de tout faire passer
par =E7a (ie. retourner un tel objet/range).

Vu que =E7a demande pas mal d'efforts, je voulais vous demander si vous
n'avez pas une meilleure id=E9e. Ou alors mon id=E9e de d=E9part n'est pas
un bon design ?

Merci,

Michael, qui revient quelques ann=E9es plus tard :)

10 réponses

1 2
Avatar
Fabien LE LEZ
On Fri, 22 Jan 2010 11:41:05 -0800 (PST), Mic Mon
:

J'aimerais mettre en place une interface à-la IEnumerable<T>.



Ou alors mon idée de départ n'est pas un bon design ?



Ben... Si tu veux programmer en Java (ou C#, c'est pareil) en C++, ne
t'attends pas à un grand enthousiasme de ma part.


Que veux-tu faire exactement ?

Tu as déjà des vector<> et des list<>, venant de sources diverses, et
tu veux les encapsuler dans une interface unique ?

Ou bien, tu veux stocker des éléments, et tu as tantôt besoin des
avantages de list<>, et tantôt des avantages de vector<> ?

Quid de la gestion de la durée de vie des conteneurs ? N'oublie pas
que C++, contrairement à C# et Java, n'a pas de GC.


j'aimerais voir avec 2 méthodes



Attention, le mot "méthode" en C++ désigne plutôt une façon d'arriver
à ses fins.
Il peut aussi servir à désigner, soit une fonction membre virtuelle,
soit une fonction membre tout court ; mais comme tout le monde n'est
pas d'accord sur la signification, mieux vaut l'éviter.

abstraites



"Classe abstraite" ou "fonction virtuelle pure". Choisis, mais ne
mélange pas les deux.


: "AddChild (Children*)" et
"getChildren (Children*)".



Que serait l'argument passé à ces fonctions ?


donc je ne pourrai pas stocker des implémentations avec
différents containers dans un même vector par exemple.



Yep. Note que le polymorphisme d'héritage ne fonctionne pas beaucoup
mieux : ça force à mettre des pointeurs dans ton conteneur, et à gérer
toi-même la durée de vie des éléments, ce qui est vite chiant.


J'essaie depuis un moment de trouver une solution élégante au
problème, mais j'en viens toujours au même point : je manque
d'informations. Pourquoi as-tu besoin de gérer à la fois vector<> et
list<> ?
Avatar
FX
Mic Mon a écrit :
Bonjour,

J'aimerais mettre en place une interface à-la IEnumerable<T>. En 2
mots, c'est une classe C# de base pour toutes les collections
enumerables, qui permet de traiter de façon uniforme list et vector
par exemple, lorsqu'on a des boucles du types "foreach ()".

J'aimerais faire la même chose, ie. j'ai une interface "Interface" que
j'aimerais voir avec 2 méthodes abstraites : "AddChild (Children*)" et
"getChildren (Children*)". Mais que donner comme type de retour à
"getChildren" ?




Je ne suis pas sûr d'avoir tout compris à ton problème, mais je pense
que getChildren() devrait retourner un type de classe abstraite,
appelons la "Iterator", qui encapsulerait le container list ou vector
(ou autre) utilisé. A chaque implémentation de Interface corresponderait
une implémentation de Iterator.

Ainsi:

// Définition de l'interface
template<class T>
class Iterator
{
T* next() = 0;
// Ou bien on pourrait définir des fonctions plus proches
// de la STL (begin(), end(), operator++, operator*)
};

template<class T>
class Interface
{
void AddChild(T*) = 0;
Iterator<T> GetChildren() = 0;
};

// Implémentation avec la classe std::list
template<class T>
class ListIterator : Iterator<T>
{
ListIterator(std::list<T*> *_list)
{
list = _list;
it = list->begin();
}
T * next()
{
it++;
if(it == list->end())
{
return NULL;
}
else
{
return *it;
}
}
std::list<T*> *list;
std::list<T*>::iterator it;
};

template<class T>
class ListInterface : Interface<T>
{
void AddChild(T* t) { list.push_back(t); }
T* GetChildre(T* t) { return ListIterator(&list); }
std::list<T*> list;
};
Avatar
Mic Mon
On 22 jan, 21:16, Fabien LE LEZ wrote:
>J'aimerais mettre en place une interface à-la IEnumerable<T>.
> Ou alors mon idée de départ n'est pas un bon design ?

Ben... Si tu veux programmer en Java (ou C#, c'est pareil) en C++, ne
t'attends pas à un grand enthousiasme de ma part.



Euh, ne soyons pas psychorigides. Je ne vois pas le problème de
vouloir récupérer les choses pratiques d'un côté pour les mettre de
l'autre. Si j'écris en C++ c'est que je préfère un paquet de choses e n
C++, mais le foreach est bien quelque chose de très pratique (d'où
BOOST_FOREACH).

Que veux-tu faire exactement ?

Tu as déjà des vector<> et des list<>, venant de sources diverses, et
tu veux les encapsuler dans une interface unique ?

Ou bien, tu veux stocker des éléments, et tu as tantôt besoin des
avantages de list<>, et tantôt des avantages de vector<> ?



Disons que je définis une interface qui dit simplement "je contiens
une collection d'objets de tel type". Et vu que c'est l'interface, je
n'ai pas envie de lier ça à une implémentation particulière qui
utiliserait std::list, std::vector, ou whatever, donc je peux pas
faire de typedef d'un iterator particulier.

Quid de la gestion de la durée de vie des conteneurs ? N'oublie pas
que C++, contrairement à C# et Java, n'a pas de GC.



Le type en question est un shared_ptr, donc la durée de vie est gérée
par l'owner (en l'occurence l'implémentation de l'interface).

>j'aimerais voir avec 2 méthodes

Attention, le mot "méthode" en C++ désigne plutôt une façon d'arr iver
à ses fins.
Il peut aussi servir à désigner, soit une fonction membre virtuelle,
soit une fonction membre tout court ; mais comme tout le monde n'est
pas d'accord sur la signification, mieux vaut l'éviter.



Ah ok, je trouvais ça plutôt pratique justement de distinguer fonction
toplevel et méthode d'une classe, mais ok.

>abstraites

"Classe abstraite" ou "fonction virtuelle pure". Choisis, mais ne
mélange pas les deux.



Oui, je suis la convention "classe abstraite" = "classe avec que des
fonctions virtuelles pures".

> : "AddChild (Children*)" et
>"getChildren (Children*)".

Que serait l'argument passé à ces fonctions ?



Zut, typo, getChildren ne prend pas d'argument.

> donc je ne pourrai pas stocker des implémentations avec
>différents containers dans un même vector par exemple.

Yep. Note que le polymorphisme d'héritage ne fonctionne pas beaucoup
mieux : ça force à mettre des pointeurs dans ton conteneur, et à g érer
toi-même la durée de vie des éléments, ce qui est vite chiant.



Peut-être que si je donne plus de contexte ça aidera. Je fais une
hiérarchie de classes pour représenter l'arbre syntaxique concret
(avant analyse sémantique et autres) d'un langage que je fais. J'ai
une interface que je nomme "IAddFunctions" qui a vocation à être
l'interface de tout objet (syntaxique) qui peut contenir une fonction
(classe, namespace, toplevel). Ces fonctions sont des implémentations
d'une interface "IFunction" qui donne les fonctionnalités de base pour
gérer une fonction du point de vue syntaxique. Comme ça le parser ne
se soucie pas de l'implémentation et instancie juste ces trucs-là par
factory. Ca découple ainsi le parser de la suite.

Je suis donc amené à stocker des pointeurs vers IFunction, donc des
shared_ptr<IFunction>, dans un conteneur. Mais je n'ai pas envie de
préciser à l'utilisateur de l'interface quel conteneur j'utilise en
interne dans l'implémentation de l'interface.

Tout ça parce que j'aimerais que ce framework passe sans pb si je veux
ajouter le support d'un autre langage, donc j'essaie d'éliminer tout
couplage inutile.

J'essaie depuis un moment de trouver une solution élégante au
problème, mais j'en viens toujours au même point : je manque
d'informations. Pourquoi as-tu besoin de gérer à la fois vector<> et
list<> ?



Disons que je ne veux juste pas fermer de porte pour d'éventuelles
futures implémentations auxquelles je ne pense pas maintenant. Mais
peut-être que j'essaie de trop généraliser ?

Mic
Avatar
Mic Mon
On 23 jan, 00:18, FX wrote:
Je ne suis pas s r d'avoir tout compris ton probl me, mais je pense
que getChildren() devrait retourner un type de classe abstraite,
appelons la "Iterator", qui encapsulerait le container list ou vector
(ou autre) utilis . A chaque impl mentation de Interface corresponderait
une impl mentation de Iterator.
[code]



Oui voilà, c'est à peu près ce que j'avais dans l'idée quand je
parlais d'adaptor. Cependant, j'ai l'impression qu'il serait plus
clair de séparer conteneut et iterator. En plus, ce qui me faisait
éviter cette solution c'est qu'il faut faire un truc pour chaque
conteneur... En fait j'en suis arrivé à un truc de ce genre dans
l'idée :

class Interface
{
public:
void AddChild (Type* p) = 0;
boost::range<IITerator<Type>, IIterator<Type> > getChildren () =
0;
};

templace <typename T>
class IIterator
{
public:
T operator* () = 0;
operator++ ... blabla
};

et après une seule implémentation pour tous les conteneurs STL :
template<typename Conteneur>
class Iterator : IIterator<Conteneur::value_type>
{
public:
typedef Conteneur::iterator stl_iter;
Iterator (stl_iter it) {...}
// suite logique, operator*, ++, ...

private:
stl_iter myiter;
};

puis une fonction make_iterator qui permet la déduction d'argument
template.

Ensuite dans une instantation de Interface, on fait simplement :

boost::range<IIterator<MonTypeConnu>, IIterator<MonTypeConnu> >
InterfaceConcrete::getChildren ()
{
// InterfaceConcrete définit std::vector<MonTypeConnu>
mon_conteneur_connu
return make_pair ( make_iterator (mon_conteneur_connu.begin(),
mon_conteneur_connu.end()));
// peut-être avec un peu d'aide pour la déduction d'argument,
j'arrive jamais à prévoir
}

Ca marcherait j'ai l'impression ? Any thought ?

Mic
Avatar
Fabien LE LEZ
On Fri, 22 Jan 2010 16:12:27 -0800 (PST), Mic Mon
:

Mais peut-être que j'essaie de trop généraliser ?



C'est bien mon impression.

Tu essaies d'implémenter un truc assez compliqué, pour pouvoir
accomoder une fonctionnalité dont tu n'es pas sûr d'avoir besoin.
C'est donc un cas typique de généralisation abusive.

Utilise plutôt des typedef pour pouvoir changer le conteneur
facilement en cas de besoin. Et en attendant, tu peux suivre une des
règles de base : utilise std::vector<> tant que tu n'as pas de bonne
raison d'utiliser autre chose. Et attends pour en changer que ton
profiler t'indique un coût important de vector<>::insert().
Avatar
Fabien LE LEZ
On Fri, 22 Jan 2010 16:12:27 -0800 (PST), Mic Mon
:

Ben... Si tu veux programmer en Java (ou C#, c'est pareil) en C++, ne
t'attends pas à un grand enthousiasme de ma part.



Euh, ne soyons pas psychorigides.



Il n'y a pas si longtemps, j'ai commencé à programmer en Javascript.
Comme je connaissais principalement C++ et PHP, j'ai tenté d'utiliser
les mêmes méthodes et paradigmes en Javascript.
Le résultat fut... lamentable. Certes, les scripts fonctionnaient,
mais les créer et les maintenir étaient pénibles.

Et puis, je suis tombé sur <http://javascript.crockford.com/>. Grâce à
ce gars, j'ai compris qu'on peut programmer élégamment en
Javascript... à condition d'accepter de programmer en Javascript.
C'est-à-dire : définir des objets (Une fonction étant un cas
particulier d'objet), et (presque) oublier la notion de classe.

Du coup, très peu de méthodes C++ sont utilisables en Javascript. On
oublie tout et on recommence à zéro.

De même, si tu décides de programmer en C++, je t'invite à oublier la
programmation C# (et vice-versa).
Avatar
Fabien LE LEZ
On Fri, 22 Jan 2010 16:31:03 -0800 (PST), Mic Mon
:

class Iterator : IIterator<Conteneur::value_type>



Ne manque-t-il pas un "public" quelque part ?

return make_pair ( make_iterator (mon_conteneur_connu.begin(),
mon_conteneur_connu.end()));



N'insère pas de retours à la ligne au milieu de tes lignes de code,
c'est très difficile à lire !

// peut-être avec un peu d'aide pour la déduction d'argument,
j'arrive jamais à prévoir



Ben justement, tu devrais regarder les types et surtout les
conversions de types.

mon_conteneur_connu.begin() est de type
std::vector<MonTypeConnu>::iterator

make_iterator (...) renvoie un objet de quel type ?
make_pair (...) renvoie un objet de quel type ?
Le type renvoyé est-il bien convertible en un
"boost::range<IIterator<MonTypeConnu>, IIterator<MonTypeConnu> >" ?

D'ailleurs, peut-on construire un objet de type
"boost::range<IITerator<Type>, IIterator<Type> >", sachant que
IIterator<Type> est une classe abstraite ?

Pour que tout ça fonctionne, il faudrait avoir des pointeurs dans tous
les sens. Or, c'est anti-idiomatique : par défaut, on travaille avec
des objets, et des copies par valeur.

En particulier, std::make_pair<> effectue une copie de ses arguments.
Du coup, il transforme deux Iterator<> en un
pair<Iterator<>,Iterator<>>.
Il ne peut pas fonctionner avec des IIterator car cette classe est
abstraite : on ne peut pas en créer d'instances.

Et même si tu peux convertir un Iterator* en un IIterator*, il n'y a a
priori aucune raison de penser qu'on peut transformer un
Machin<Iterator> en un Machin<IIterator>.

Enfin bref, ça m'étonnerait que ça fonctionne.
Avatar
ld
On 22 jan, 20:41, Mic Mon wrote:
Bonjour,

J'aimerais mettre en place une interface à-la IEnumerable<T>. En 2
mots, c'est une classe C# de base pour toutes les collections
enumerables, qui permet de traiter de façon uniforme list et vector
par exemple, lorsqu'on a des boucles du types "foreach ()".



quelle est le probleme avec la STL ?

http://www.cppreference.com/wiki/stl/iterators
http://www.cppreference.com/wiki/stl/algorithm/for_each
http://www.cppreference.com/wiki/stl/algorithm/transform

Je pense qu'il faut bien definir ton probleme et distinguer deux cas.
Soit le conteneur est connu a la compilation et dans ce cas la STL
devrait satisfaire la pluspart des besoins (cf liens ci-dessus). Soit
le conteneur n'est pas connu a la compilation et dans ce cas il y a
beaucoup a faire/ecrire pour arriver au meme niveau que la STL et
mieux vaut chercher une bibliotheque qui fait deja cela.

D'une maniere generale, si les types sont connus a la compilation, C++
est tres rapide et tres expressif grace aux templates ce qui permet
une tres bonne abstraction du plus bas niveau au plus haut niveau.
C'est aussi un des seuls langages statiquement types (le seul?)
capable de faire de la programmation generative (toujours grace aux
templates) ce qui permet de specialiser des types/algorithmes pour une
utilisation specifique, la ou les autres langages auraient une
explosion combinatoire de cas a traiter (un peu comme l'evaluation
lazy de Haskell mais a la compilation).

Sinon ce n'est pas forcement le bon langage car l'utiliser dans de tel
cas reviendrait a ecrire un "interpreteur". Ce n'est biensur pas
impossible, mais cela veut dire ecrire une sorte de framework a-la-STL
en tout dynamique que l'on appelle un "Adaptive Object Model". Mais si
tel est ton besoin, peut-etre considerer un langage plus dynamique
permettra de resoudre le probleme plus efficacement.

Vu que ça demande pas mal d'efforts, je voulais vous demander si vous
n'avez pas une meilleure idée. Ou alors mon idée de départ n'est pa s
un bon design ?



Peut-etre regarder du cote de Boost, Qt, Poco, Root pour voir ce qui a
deja ete fait serait un gain de temps? Une alternative rapide
consisterait a utiliser la STL (ou boost) avec boost::variant ou
boost::any suivant les besoins.

Michael, qui revient quelques années plus tard :)



Laurent (qui est parti depuis quelques annees ;-)
Avatar
FX
Fabien LE LEZ a écrit :

class Iterator : IIterator<Conteneur::value_type>



Ne manque-t-il pas un "public" quelque part ?



Effectivement, un peu psychorigide :-)


return make_pair ( make_iterator (mon_conteneur_connu.begin(),
mon_conteneur_connu.end()));





Comme Fabien, je ne comprends pas ce qu'est cette fonction make_pair à
un seul argument.


>> boost::range<IIterator<MonTypeConnu>, IIterator<MonTypeConnu> >
>> InterfaceConcrete::getChildren ()

Je ne comprends pas non plus l'intérêt d'utiliser boost::range. Mais je
ne connais pas bien cette librairie.


> D'ailleurs, peut-on construire un objet de type
> "boost::range<IITerator<Type>, IIterator<Type> >", sachant que
> IIterator<Type> est une classe abstraite ?

Bonne remarque ! L'utilisation de la classe abstraite amène certaines
restrictions.
Avatar
Stan
On 22 jan, 20:41, Mic Mon wrote:
Bonjour,

J'aimerais mettre en place une interface à-la IEnumerable<T>. En 2
mots, c'est une classe C# de base pour toutes les collections
enumerables, qui permet de traiter de façon uniforme list et vector
par exemple, lorsqu'on a des boucles du types "foreach ()".




Si l'ensemble commun se limite à quelques services
du type 'foreach', je pense qu'il est plus judicieux de
créer des fonctions paramètrées encapsulées dans
un namespace.

As-tu pensé à cette solution ?

Je parts du principe qu'une solution simple qui couvre
80% des besoins est préférable à une solution complexe
qui couvre 98% des besoins.

--
-Stan
1 2