2009-03-11 6 views
3

J'ai une situation où je marche par un vecteur, de faire les choses:Quel est le moyen le plus propre de marcher et de désenvoyer un vecteur std :: en utilisant des itérateurs?

 
std::vector::iterator iter = my_list.begin(); 

for (; iter != my_list.end(); ++iter) 
{ 
    if (iter->doStuff()) // returns true if successful, false o/w 
    { 
    // Keep going... 
    } 
    else 
    { 
    for (; iter != m_list.begin(); --iter) // ...This won't work... 
    { 
     iter->undoStuff(); 
    } 
    } 
} 

Dans des conditions normales - en supposant que tout se passe bien - je marche tout le chemin à my_list.end() et terminer la boucle avec succès.

Cependant, si quelque chose ne va pas pendant que je fais des choses, je veux pouvoir tout annuler - revenez essentiellement au tout début du vecteur, en défaisant tout un à la fois dans l'ordre inverse.

Mon problème est que lorsque j'arrive à my_list.begin() - comme le montre la boucle for imbriquée - je n'ai vraiment pas encore terminé car j'ai encore besoin d'appeler undoStuff() sur mon premier élément de la liste. Maintenant, je pourrais juste faire l'appel final en dehors de la boucle, mais cela semble un peu sale.

La façon dont je le vois, je n'ai terminé que lorsque j'arrive à my_list.rend(). Cependant, je ne peux pas comparer un std :: vector :: iterator à un std :: vector :: reverse_iterator. Étant donné ce que j'essaie de faire, quel est le meilleur choix de combinaison de type itérateur/boucle?

Répondre

4

Lors de l'utilisation itérateurs inverse via rbegin() et rend() fonctionne bien, malheureusement, je trouve que la conversion entre reverse et non-reverse iterarotrs a tendance à être assez déroutant. Je ne peux jamais me souvenir sans avoir à passer par un exercice de logique-puzzle si j'ai besoin d'incrémenter ou de décrémenter avant ou après la conversion. Par conséquent, j'évite généralement la conversion.

Voici comment je coderais probablement votre boucle de gestion des erreurs. Notez que je pense que vous n'auriez pas besoin d'appeler undoStuff() pour l'itérateur qui a échoué - après tout, doStuff() a déclaré qu'il n'a pas réussi.

// handle the situation where `doStuff() failed... 

// presumably you don't need to `undoStuff()` for the iterator that failed 
// if you do, I'd just add it right here before the loop: 
// 
//  iter->undoStuff(); 

while (iter != m_list.begin()) { 
    --iter; 
    iter->undoStuff(); 
} 
+0

Une manière simple de penser aux itérateurs est qu'ils sont des curseurs aux positions entre les éléments. Un itérateur avant produira l'élément après le curseur lorsqu'il sera déréférencé, un itérateur inverse produira l'élément avant le curseur lorsqu'il sera déréférencé. Les itérateurs avant et arrière équivalents sont des curseurs qui sont à la même position. – Mankarse

8

Je suis un peu rouillé quand il s'agit de vecteurs STL, mais serait-il possible de créer un std::vector::reverse_iterator à partir de votre itérateur initial? Ensuite, vous n'auriez besoin de commencer au dernier article que vous étiez à l'avenir, et serait en mesure de le comparer à my_list.rend() pour s'assurer que le premier article est traité.

+0

oui, vous pouvez le faire. voir ici: http://www.gamedev.net/community/forums/topic.asp?topic_id=388555 –

4

Il n'y a bien sûr aucune raison de ne pas utiliser les vecteurs operator[]() si cela rend votre code plus clair, plus simple et/ou plus efficace.

+0

Pas plus efficace dans la plupart des cas. Les itérateurs sont des pointeurs abstraits pour les types de données STL. [] fonctionne horriblement pour les listes chaînées (STL), par exemple. – strager

+0

Eh bien, j'ai dit "si" :-) –

+0

Et je ne savais pas que std :: liste avait un opérateur [] –

1

Vous devez utiliser rbegin() pour obtenir un itérateur réversible.

Personnellement, je préfère encore

for (int i=0;i<vecter.size();i++) { } 
2

Sans l'aide d'un reverse_iterator, vous pouvez marcher à reculons cette façon:

while(iter-- != m_list.begin()) 
{ 
    iter->undoStuff(); 
} 

Bien que cela crée une copie de iter, le coût ne doit pas être trop grande . Vous pouvez factoriser pour une meilleure vitesse:

while(iter != m_list.begin()) 
{ 
    --iter; 
    iter->undoStuff(); 
} 
+0

En cas de défaillance du premier élément, la boucle while n'est jamais entrée? – Runcible

+0

@Runcible, Ah, c'est vrai. Je n'ai pas remarqué ça. Pardon. Je vais essayer de mettre à jour ma réponse pour résoudre ce problème. – strager

+0

@Runcible: devriez-vous appeler undoStuff() sur l'itération qui a échoué à l'appel doStuff()? Bien sûr, cela dépend du comportement des méthodes, mais souvent vous ne le feriez pas (c'est-à-dire que vous n'appelez pas fclose() pour un fopen() qui a échoué). –

1

Ok, je vais aller sur une branche ici ..

std::vector iterator iter = my_list.begin(); 
bool error = false; 

while(iter != my_list.end()) 
{ 
    error = !iter->doStuff(); 
    if(error) 
    break 
    else 
    iter++; 
} 

if(error) 
do 
{ 
    iter->undoStuff(); 
    iter--; 
} 
while(iter != my_list.begin()) 
+0

Peut-être que j'ai mal lu ceci - mais s'il y a une erreur sur le premier élément, il semble que l'iter-- dans la deuxième boucle sera hors de portée et fera quelque chose de mauvais? – Runcible

+0

Je pense que vous avez raison, à tout le moins, la décrémentation d'un itérateur begin() peut être indéfinie. Dommage, il franchira définitivement la ligne laide en ayant à remplacer iter-- avec if (iter! = My_list.begin()) iter--; :) –

0

Ce que j'appelle l'ingénierie sur, mais il est tellement amusant

// This also can be done with adaptators I think 
// Run DoStuff until it failed or the container is empty 
template <typename Iterator> 
Iterator DoMuchStuff(Iterator begin, Iterator end) { 
    Iterator it = begin; 
    for(; it != end; ++it) { 
    if(!*it->DoStuff()) { 
     return it; 
    } 
    } 
    return it; 
} 

// This can be replaced by adaptators 
template <typename Iterator> 
void UndoMuchStuff(Iterator begin, Iterator end) { 
    for(Iterator it = begin; it != end; ++it) { 
    it->UndoStuff(); 
    } 
} 

// Now it is so much easier to read what we really want to do 
typedef std::vector<MyObject*> MyList; 
typedef MyList::iterator Iterator; 
typedef MyList::reverse_iterator ReverseIterator; 
Iterator it = DoMuchStuff(my_list.begin(), my_list.end()); 
if(it != my_list.end()) { 
    // we need to unprocess [begin,it], ie including it 
    UndoMuchStuff(ReverseIterator(1+it), ReverseIterator(my_list.begin())); 
} 
2

Cela dépend de ce que votre fonction doStuff() fait, et l'importance de la performance est dans votre contexte. Si possible, il serait probablement plus clair (c'est-à-dire - plus facile pour le lecteur) de travailler sur une copie de votre vecteur, et seulement si tout va bien, permutez les vecteurs.

std::vector<Foo> workingCopy; 
workingCopy.assign(myVector.begin(), myVector.end()); 

bool success = true; 
auto iter = workingCopy.begin(); 
for(; iter != workingCopy.end() && success == true; ++iter) 
    success = iter->doStuff(); 

if(success) 
    myVector.swap(workingCopy); 
+0

+1 pour l'utilisation de RAII! – Mankarse

+0

Je voudrais simplement utiliser le constructeur de copie 'std :: vector'' et dire' std :: vector workingCopy = myVector; '. Stylistiquement, je préférerais soit avoir throw doStuff (en supposant que c'est une sorte d'opération complexe, je préfère les exceptions quand il y a une sorte de chaîne profonde d'appels qui pourraient échouer à tout point au milieu) ou dire 'for (auto iter = workingCopy.begin(); iter! = workingCopy.end(); ++ iter) {if (! iter-> doStuff()) renvoie false; } return true; ' Et que cela soit sa propre fonction qui utilise workingCopy par référence. Utilisez la valeur de retour de cela pour déterminer si vous souhaitez échanger. –

0

Cela peut être fait avec un reverse_iterator:

bool shouldUndo(false); 
std::vector::iterator iter(my_list.begin()), end(my_list.end()); 
for (; iter != end && !shouldUndo; ++iter) 
{ 
    shouldUndo = iter->doStuff(); // returns true if successful, false o/w 
} 
if (shouldUndo) { 
    reverse_iterator<std::vector::iterator> riter(iter), rend(my_list.rend()); 
    //Does not call `undoStuff` on the object that failed to `doStuff` 
    for (; riter != rend; ++riter) 
    { 
    iter->undoStuff(); 
    } 
} 
Questions connexes