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

implementer un iterateur de X sur un std::vector< std::vector >

31 réponses
Avatar
meow
Tout est dit dans le titre.
Pour faire court j'ai une classe qui contient un vector< vector X >
(o=F9 X est une classe) et j'aimerai it=E9rer sur tous les X contenus
dans ce vecteur de vecteurs. Je m'interroge donc sur la meilleure
fa=E7on de faire. J'imagine que la probl=E9matique est classique mais je
n'ai rien trouv=E9 sur le sujet.

On m'a souffl=E9 une solution facile : ma classe it=E9rateur contiendrait
un vector< X >, mise =E0 plat du vector<vector<X>> ainsi qu'un
it=E9rateur sur ce vecteur (appellons le it).
Je d=E9finirais alors mes op=E9rateurs ++, * et Cie simplement en les
d=E9l=E9guants =E0 it (i.e le ++ de mon it=E9rator fait juste un ++it, et
le * renvoie *it).

Cette solution me parrait lourdingue : si les vector<X> contiennent
beaucoup de X, mon it=E9rateur va bouffer beaucoup de m=E9moire. En
outre, contrairement aux it=E9rateurs de la stl, si ma structure tombe,
mon it=E9rateur continue =E0 etre valide (est-ce important ? Je suppose
que =E7a l'est si je veux faire des delete, replace, remove etc... mais
sinon ?) J'ai donc pens=E9 =E0 une autre solution :

dans mon_iterateur je stockerais un tableaux de paires d'it=E9rateurs
sur X. Chaque paire =E9tant constitu=E9e d'un it=E9rateur sur le d=E9but et
d'un it=E9rateur sur la fin d'un =E9l=E9ment de mon vector<vector<X>>.
J'aurai en outre encore un it=E9rateur sur mon vecteur de paire.
en gros :
class mon_iterateur {
vector<pair<vector<X>::iterator,vector<X>::iterator>> ;
vector<pair<vector<X>::iterator,vector<X>::iterator>> it;
.=2E.
mon_iterateur& operator++ {
if ((*it).first =3D=3D (*it).second) ++it else ++(*it);
return *this;
}
.=2E.
};

=E7a vous semble correct ? y a t'il mieux ?

10 réponses

1 2 3 4
Avatar
Fabien LE LEZ
On 15 Jan 2007 01:35:27 -0800, "meow" :

for (ConstIterVV<int> i; i != ConstIterVV<int>(); ++i)
C'est ce que j'avais cru comprendre, mais ce qui me dérange dans cette

écriture, c'est le fait que le begin se réfère bien à une structure
(celle sur laquelle on itère), alors que ce n'est pas le cas du end().


Ça, c'est pas bien méchant. D'ailleurs, istream_iterator<> fait
pareil.
Comme l'a dit James, tu peux créer des fonctions "begin" et "end" qui
prennent chacun le vector<> en paramètre, même si la seconde ne
l'utilise pas.

En lisant ce que vous avez écrit je me rends compte que finalement mon
problème est plus profond et que pour commencer je ne sais pas trop
bien ce qu'on appelle "itérateur".
[...]et il ne démord pas du fait que c'est
là ce qu'on appelle *itérateur*.


Le problème de fond, c'est que chacun a sa propre définition d'un
itérateur.

Généralement, en C++, quand on parle d'"itérateur" sans autre
précision, ça signifie "itérateur façon STL".
Ce n'est vraisemblablement pas la notion d'itérateur la meilleure,
mais c'est la plus connue, et la seule incluse dans le standard.

De plus, si on me demande d'ajouter à std:vector<...> un nouveau type
d'itérateur, comme tu l'as demandé, j'ai tendance à utiliser le même
type d'itérateurs que vector<>.

Bref, si je ne me trompe pas, les itérateurs sTL ne sont pas à
proprement parler des itérateurs ?!


Les itérateurs de la STL sont la généralisation de la notion de
pointeur. En d'autres termes, un pointeur est un itérateur à part
entière.

D'ailleurs, dans certaines (vieilles) implémentations de la STL, un
std::vector<T>::iterator est en fait un T*.
Ce n'est pas absolument conforme, mais en pratique, ça fonctionne.


Avatar
Fabien LE LEZ
On 15 Jan 2007 01:35:27 -0800, "meow" :

En fait, je pense au cas où ton VV est masqué à l'intérieur d'une
classe (disons A), et que tu demandes tes itérateurs par a.begin() et
a.end() (avec a, instance de A). Ta condition d'arret en intérrant sur
a fonctionnerait tout aussi bien avec b.end()


Si je ne m'abuse, le code suivant a un comportement indéfini :

typedef ... Tableau; // vector<int> par exemple
Tableau t1, t2;
// remplissage

for (Tableau::const_iterator i= t1.begin(); i!=t2.end(); ++i)
{ ... }

L'expression "comportement indéfini" est claire : on ne peut pas
prévoir ce que ça va faire. Ça peut donner quelque chose de bon les
nuits de pleine lune uniquement, ou fonctionner parfaitement tout le
temps, sauf avec une version précise de g++, etc.

Avec "typedef ConstIterVV<int> Tableau", ça fonctionne tout le temps.
C'est pas bien méchant ; de toutes façons, le code n'a pas de sens.

Avatar
James Kanze
meow wrote:

@ Fabien LE LEZ
for (ConstIterVV<int> i; i != ConstIterVV<int>(); ++i)
C'est ce que j'avais cru comprendre, mais ce qui me dérange dans cette

écriture, c'est le fait que le begin se réfère bien à une structu re
(celle sur laquelle on itère), alors que ce n'est pas le cas du end().
En fait, je pense au cas où ton VV est masqué à l'intérieur d'une
classe (disons A), et que tu demandes tes itérateurs par a.begin() et
a.end() (avec a, instance de A). Ta condition d'arret en intérrant sur
a fonctionnerait tout aussi bien avec b.end() (b autre instance de A).
Pour "résoudre" ça, il suffirait d'inclure une condition sur
l'adresse du vecteur pointé... Mais meme comme ça, ça me dérange,
bien qu'honnètement je ne saurai dire pourquoi.


Si tu as une collection, rien n'empèche d'implémenter des
fonctions begin() et end(), avec end() :

IterVV< T >
CollectionVV< T >::end()
{
return IterVV< T >() ;
}

Le client n'a besoin de savoir que end() ne se sert pas de la
collection que s'il veut. De même avec mes fonctions globales :
si j'appelle un algorithme standard, j'écris e.g. :

std::find( begin( collection ), end( collection ), whatever) ;

Quand je me sers de l'idiome STL, que m'importe les informations
dont a besoin l'itérateur de fin.

@ Kanze & Fabien
En lisant ce que vous avez écrit je me rends compte que finalement mon
problème est plus profond et que pour commencer je ne sais pas trop
bien ce qu'on appelle "itérateur". La "solution" proposée par Kanze
avec des fonctions has_next(), next(), value() m'a été proposée par
un collègue qui fait du Java, et il ne démord pas du fait que c'est
là ce qu'on appelle *itérateur*.


C'est effectivement ce qu'on a toujours appelé un itérateur.
Voir par exemple dans les modèles de conception (le GoF).

Note que le Java ne le fait pas vraiment comme il faut non plus,
parce qu'il utilise la même fonction pour avancer et pour
déréférencer, deux opérations distinctes. C'est ce que faisait
les collections dans la bibliothèque USL, et on s'est vite rendu
compte des problèmes (moins genant que la nécessité de deux
itérateurs, mais genant quand même).

Après cogitation, cette solution me semble plus séduisante, parce
qu'elle colle pls évidemment à ce qu'on a envie d'appeller
itérateur.


Tout à fait.

Donc -arretez moi si je me trompe- tout le tintouin à coup de "++" et
de "*" aurait été proposé par les cerveaux de la STL dans le soucis
de répondre à un autre type de problématique, connexe à celle des
itérateurs, mais pas tout a fait avec le meme cahier des charges.


Ça, c'est une autre thème. Pour moi, il y a bien abus de
surcharge. Mais ça se discute : utiliser ++ comme nom de
fonction pour aller à l'élément suivant, et * pour accéder à
l'élément courant, ça ne change rien de fondamental ; c'est
juste la façon qu'on épelle les fonctions. Avoir besoin de deux
objets pour déterminer la fin, en revanche, c'est franchement
pénible.

A
première vue ce que je vois comme différence majeure, c'est le fait
qu'un itérateur (au sens java (faute de trouver un meilleur terme) )


Au sens classique.

est capable de dire quand il est arrivé au bout de sa structure
(fonction has_next) et qu'il n'a pas besoin d'etre comparé à un autre
itérateur pour ce faire; ce qui n'est pas le cas des itérateurs
STL...


Tout à fait. Il y a deux grands problèmes avec l'idiome à deux
itérateurs :

-- Certains itérateurs ont besoin de connaître la fin lors des
incrémentations. L'exemple type, c'est un itérateur
filtrant, c-à-d un itérateur qui ne s'arrête qu'aux certains
éléments.

-- Quand une fonction renvoie une séquence, elle est obligée à
renvoyer deux valeurs. Ce qu'on ne peut pas passer
directement comme paramètre à une autre fonction. On ne peut
donc pas facilement enchaîner les fonctions.

Quand à savoir en quoi le design de la stl necessitait ces
modifications au paradigme d'itérateur, pour moi c'est encore flou.
Bref, si je ne me trompe pas, les itérateurs sTL ne sont pas à
proprement parler des itérateurs ?!


Pas vraiment. Ils se comportent plutôt des pointeurs intelligents.

--
James Kanze (GABI Software) email:
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34


Avatar
James Kanze
Fabien LE LEZ wrote:

[...]
Le problème de fond, c'est que chacun a sa propre définition d'un
itérateur.


Maintenant. Le concepte d'itérateur n'est pas vraiment récent,
et il y avait bien une entente sur sa signification, au moins
dans les langages OO et procéduraux. (Je ne m'avancerai pas sur
la signification d'itérateur dans un langage fonctionnel.) Puis,
arrive la STL, qui utilise le mot pour quelque chose
complétement différent.

[...]
Bref, si je ne me trompe pas, les itérateurs sTL ne sont pas à
proprement parler des itérateurs ?!


Les itérateurs de la STL sont la généralisation de la notion de
pointeur. En d'autres termes, un pointeur est un itérateur à part
entière.


Seulement parce qu'on a changé la signification d'itérateur.

--
James Kanze (GABI Software) email:
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34


Avatar
Fabien LE LEZ
On 15 Jan 2007 09:49:39 -0800, "James Kanze" :

Bref, si je ne me trompe pas, les itérateurs sTL ne sont pas à
proprement parler des itérateurs ?!


Pas vraiment. Ils se comportent plutôt des pointeurs intelligents.


Pour décrire les itérateurs de la STL, j'emploierais plutôt
l'expression "pointeurs généralisés". Les pointeurs intelligents,
c'est encore autre chose (des classes qui gèrent la durée de vie
d'objets).


Avatar
James Kanze
Fabien LE LEZ wrote:
On 15 Jan 2007 09:49:39 -0800, "James Kanze" :

Bref, si je ne me trompe pas, les itérateurs sTL ne sont pas à
proprement parler des itérateurs ?!


Pas vraiment. Ils se comportent plutôt des pointeurs intelligents.


Pour décrire les itérateurs de la STL, j'emploierais plutôt
l'expression "pointeurs généralisés". Les pointeurs intelligents,
c'est encore autre chose (des classes qui gèrent la durée de vie
d'objets).


Un pointeur intelligent ne gèrent pas forcément la durée de vie
d'un objet ; ce n'en est qu'une des utilisations. En revanche,
en effet, je préfère bien « pointeur généralisé » dans ce
cas-ci. Parce que l'intelligence, je ne l'y vois pas.

--
James Kanze (GABI Software) email:
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34



Avatar
meow
@Fabien
L'expression "comportement indéfini" est claire
OK, je me range à ton argument... Cela dit, ce qui me chagrine

maintenant, c'est le fait qu'on puisse avoir des "comportements
indéfinis" ;)

@James
Note que le Java ne le fait pas vraiment comme il faut non plus,
parce qu'il utilise la même fonction pour avancer et pour
déréférencer, deux opérations distinctes. C'est ce que faisait
les collections dans la bibliothèque USL, et on s'est vite rendu
compte des problèmes (moins genant que la nécessité de deux
itérateurs, mais genant quand même).
Juste pour etre certain : lorsque tu parles de la necessité de deux

itérateurs, tu fais bien référence à l'itérateur qu'on
incrémente, et à celui qui pointe sur la fin et qu'on utilise pour
comparer au premier ?

Merci encore à tous pour ces eclaircissements

Avatar
Fabien LE LEZ
On 17 Jan 2007 01:24:20 -0800, "meow" :

Cela dit, ce qui me chagrine
maintenant, c'est le fait qu'on puisse avoir des "comportements
indéfinis"


Malheureusement, il y en a pas mal en C++.

Juste pour etre certain : lorsque tu parles de la necessité de deux
itérateurs, tu fais bien référence à l'itérateur qu'on
incrémente, et à celui qui pointe sur la fin et qu'on utilise pour
comparer au premier ?


Entre autres.

Si tu veux trier les éléments d'un vector<>, tu écriras :

std::sort (v.begin(), v.end());

Il n'y a pas vraiment là de notion d'incrémentation (du moins, pas
visible), mais on a tout de même deux itérateurs.

On se retrouve à écrire quelque chose comme
"... v.begin(), v.end() ..." toutes les cinq minutes.

Avatar
Marc Boyer
Le 17-01-2007, meow a écrit :
@Fabien
L'expression "comportement indéfini" est claire
OK, je me range à ton argument... Cela dit, ce qui me chagrine

maintenant, c'est le fait qu'on puisse avoir des "comportements
indéfinis" ;)


Ne pas vouloir de "comportement indéfini", c'est être prêt
à payer le surcoût de vérifications systématiques.
Ceci dit, rien dans la norme n'empèche donc un compilo
de spécialiser "comportement indéfini" en quelque chose de
défini. Est-ce qu'il y a un marché pour un tel compilo ?
Je ne sais pas répondre.

Marc Boyer
--
Si tu peux supporter d'entendre tes paroles
Travesties par des gueux pour exciter des sots
IF -- Rudyard Kipling (Trad. André Maurois)


Avatar
Fabien LE LEZ
On Wed, 17 Jan 2007 12:23:34 +0000 (UTC), Marc Boyer :

Ceci dit, rien dans la norme n'empèche donc un compilo
de spécialiser "comportement indéfini" en quelque chose de
défini.


Certes. Mais ça m'étonnerait qu'un éditeur de compilo soit capable de
prévoir tous les cas.

Par contre, il est courant qu'un compilo ait un comportement défini
dans certains cas.

Exemple :

std::vector<int> v;
++v[0];

Selon la norme, le comportement est indéfini.
Sous VC++ en mode "release", idem.
Sous VC++ en mode "debug", le comportement est parfaitement défini. Je
ne m'en souviens plus, mais il me semble que c'est un abort().

1 2 3 4