2008-10-09 6 views
34

J'écris du code multiplateforme entre Windows et Mac. Si list :: end() "renvoie un itérateur qui adresse l'emplacement suivant le dernier élément d'une liste" et qui peut être vérifié lors de la traversée d'une liste, quel est le meilleur moyen de reculer?Comment faire une itération arrière dans une liste STL?

Ce code workson le Mac, mais pas sous Windows (ne peut pas décrémenter au-delà de premier élément):

list<DVFGfxObj*>::iterator iter = m_Objs.end(); 
for (iter--; iter!=m_Objs.end(); iter--)// By accident discovered that the iterator is circular ? 
{ 
} 

cela fonctionne sous Windows:

list<DVFGfxObj*>::iterator iter = m_Objs.end(); 
    do{ 
     iter--; 
    } while (*iter != *m_Objs.begin()); 

Y at-il une autre façon de traverser vers l'arrière que pourrait être mis en œuvre dans une boucle for?

+1

Ce serait seulement un accident de mise en œuvre que votre premier exemple (l'itérateur circulaire, comparant avec end()) fonctionnerait. – Justsalt

Répondre

60

Utilisez reverse_iterator au lieu de l'itérateur. Utilisez rbegin() & rend() au lieu de begin() & end().

Une autre possibilité, si vous aimez utiliser la macro BOOST_FOREACH, est d'utiliser la macro BOOST_REVERSE_FOREACH introduite dans Boost 1.36.0.

+0

La documentation pour itérateur et reverse_iterator est presque la même. un itérateur est bidirectionnel alors quel est le diff? – AlanKley

+2

La différence est que vous faites toujours "++ Iter" pour incrémenter l'itérateur, contre "--Iter". Ou ai-je tort? – steffenj

+0

Non, vous avez raison, ce qui est un peu étrange à incrémenter pour revenir en arrière, mais aussi logique. Bien que le reverse_iterator semble inutile étant donné que l'itérateur est bidirectionnel. Les documents pour reverse_iterator indiquent qu'il agit sur une liste inversée; sûrement, il n'inverse pas la liste interne en premier. – AlanKley

13

Vous voulez probablement les itérateurs inverses. De mémoire:

list<DVFGfxObj*>::reverse_iterator iter = m_Objs.rbegin(); 
for(; iter != m_Objs.rend(); ++iter) 
{ 
} 
+1

Merci ça sonne bien. Mais il semble aussi un gâchis de créer un reverse_iterator spécial lorsque l'itérateur est supposé être bidirectionnel – AlanKley

+0

il devrait être "...> :: reverse_iterator iter = ..." – steffenj

+0

@AlanKley Je pense que la boucle for que vous avez mise ta question va bien. La raison pour laquelle je pense que cela fonctionne est que la fonction membre .end() retourne une valeur sentinelle qui est assignée comme la valeur du pointeur suivant du dernier élément et également le pointeur prev sur le premier élément. –

4

Comme déjà mentionné par Ferruccio, l'utilisation reverse_iterator:

for (std::list<int>::reverse_iterator i = s.rbegin(); i != s.rend(); ++i) 
5

Cela devrait fonctionner:

list<DVFGfxObj*>::reverse_iterator iter = m_Objs.rbegin(); 
for (; iter!= m_Objs.rend(); iter++) 
{ 
} 
16

La meilleure/meilleure façon d'inverser itérer une liste est (comme déjà indiqué) d'utiliser des itérateurs inverses rbegin/rend.

Cependant, je tenais à mentionner que les itérateurs inverses sont implémentés en stockant la position de l'itérateur «courant» au cas par cas (au moins sur l'implémentation GNU de la bibliothèque standard).

Ceci est fait pour simplifier la mise en œuvre, pour la plage en sens inverse pour la même sémantique que toute une gamme avant [début, fin) et [rbegin, Déchirez)

Ce que cela signifie est que déréférencement une iterator consiste à créer un nouveau temporaire, et décrémenter ensuite, chaque fois:

reference 
    operator*() const 
    { 
_Iterator __tmp = current; 
return *--__tmp; 
    } 

Ainsi, déréférencement un reverse_iterator est plus lent qu'un iterator normal.

Cependant, vous pouvez utiliser à la place les itérateurs bidirectionnels réguliers pour simuler inverse itération vous, en évitant cette surcharge:

for (iterator current = end() ; current != begin() ; /* Do nothing */) 
{ 
    --current; // Unfortunately, you now need this here 
    /* Do work */ 
    cout << *current << endl; 
} 

Les essais ont montré que cette solution soit environ 5 fois plus rapide pour chaque déréférencer utilisé dans la corps de la boucle.

Remarque: Le test n'a pas été effectué avec le code ci-dessus, car std :: cout aurait été le goulot d'étranglement.Remarque: la différence d'heure de l'horloge murale était d'environ 5 secondes avec une taille std :: list de 10 millions d'éléments. Donc, de façon réaliste, à moins que la taille de vos données ne soit si grande, il suffit de s'en tenir à rbegin() rend()!

+0

En regardant à nouveau, probablement vous voulez juste initialiser avec courant = --end(); et laissez l'incrément dans la boucle for. Cela permettrait également de se prémunir contre un tableau vide que ma version ci-dessus ne contient pas. Je vais laisser l'original affiché pour le moment puisque je n'ai pas testé. – mmocny

+0

Je ne pense pas que cela fonctionnerait à moins que vous ne changiez aussi votre condition de boucle. Sinon, vous manquerez le premier élément (parce que la boucle ne sera pas exécutée si 'current == begin()') – Griddo

+0

La première ligne de la boucle for fait un décrément, donc oui, je pense. Il suffit d'écrire un test rapide pour l'essayer! Quoi qu'il en soit, je ne pense plus que ce soit la meilleure façon d'utiliser cette expression, et certains tests récents que j'ai exécutés ne montrent plus une amélioration marquée de la vitesse pour inverser les itérateurs en pleine optimisation. Cependant, sa valeur encore noter ce qui se passe sous le capot et les tests en conséquence! – mmocny

Questions connexes