2009-03-11 7 views
3

Ich habe eine Situation, wo ich durch einen Vektor marschieren, Dinge zu tun:Was ist der sauberste Weg, um einen std :: vector mit Iteratoren zu bearbeiten?

 
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(); 
    } 
    } 
} 

Unter normalen Bedingungen - alles unter der Annahme gut geht - ich die ganzen Weg zu my_list.end() marschieren und die Schleife erfolgreich zu beenden.

Wenn jedoch etwas schief geht, während ich Sachen mache, möchte ich alles rückgängig machen können - im Grunde genommen meine Schritte bis zum Anfang des Vektors zurückverfolgen und alles nacheinander in umgekehrter Reihenfolge rückgängig machen.

Mein Problem ist, dass, wenn ich zu my_list.begin() - wie in der verschachtelten for-Schleife gezeigt - ich bin wirklich noch nicht fertig, weil ich immer noch undoStuff() auf meinem ersten Element in der Liste aufrufen muss. Jetzt könnte ich den letzten Anruf außerhalb der Schleife machen, aber das scheint ein wenig unrein zu sein.

So wie ich es sehe, bin ich erst fertig, wenn ich zu my_list.rend() komme. Ich kann jedoch einen std :: vector :: iterator nicht mit einem std :: vector :: reverse_iterator vergleichen.

Gegeben, was ich versuche zu tun, was ist die beste Wahl von Iterator-Typ/Loop-Kombination?

Antwort

4

Bei der Verwendung von Reverse-Iteratoren über rbegin() und rend() funktioniert gut, leider finde ich, dass die Umwandlung zwischen reversen und nicht-reversen Iteratoren ist eher verwirrend. Ich kann mich nie erinnern, ohne eine Logik-Puzzle-Übung durchlaufen zu müssen, ob ich vor oder nach der Konvertierung inkrementieren oder dekrementieren muss. Daher vermeide ich generell die Konvertierung.

Hier ist die Art, wie ich Ihre Fehlerbehandlung Schleife wahrscheinlich codieren würde. Beachten Sie, dass ich denke, dass Sie undoStuff() für den Iterator, der fehlgeschlagen ist, nicht aufrufen müssen - schließlich sagte doStuff(), dass es nicht erfolgreich war.

// 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

Eine einfache Denkweise über Iteratoren ist, dass sie Cursor an Positionen zwischen Elementen sind. Ein Vorwärts-Iterator wird das Element nach dem Cursor erzeugen, wenn dereferenziert wird, ein Rückwärts-Iterator wird das Element vor dem Cursor erzeugen, wenn dereferenziert wird. Äquivalente Vorwärts- und Rückwärts-Iteratoren sind Cursor, die sich an der gleichen Position befinden. – Mankarse

8

Ich bin ein wenig eingerostet, wenn es um STL-Vektoren geht, aber wäre es möglich, eine std::vector::reverse_iterator von Ihrem ursprünglichen Iterator zu erstellen? Dann müssten Sie nur bei dem letzten Element beginnen, in dem Sie waren, wenn Sie fortfahren, und können es mit my_list.rend() vergleichen, um sicherzustellen, dass das erste Element verarbeitet wird.

+0

ja, können Sie das tun. siehe hier: http://www.gamedev.net/community/forums/topic.asp?topic_id=388555 –

4

Es gibt natürlich keinen Grund, die Vektoren operator[]() nicht zu verwenden, wenn das Ihren Code klarer, einfacher und/oder effizienter macht.

+0

In den meisten Fällen nicht effizienter. Iteratoren sind abstrahierte Zeiger für STL-Datentypen. [] funktioniert furchtbar für (AWL) verknüpfte Listen, zum Beispiel. – strager

+0

Nun, ich habe "wenn" gesagt :-) –

+0

Und ich war mir nicht bewusst, dass std :: list hatte einen Operator [] –

1

Sie müssen rbegin() verwenden, um einen reversiblen Iterator zu erhalten.

Persönlich bevorzuge ich noch

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

Ohne eine reverse_iterator zu verwenden, können Sie rückwärts auf diese Weise gehen:

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

Obwohl dies eine Kopie iter schafft, sollten die Kosten nicht zu groß sein . Sie können für eine bessere Geschwindigkeit Refactoring:

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

Wenn beim ersten Element ein Fehler auftritt, wird die while-Schleife nie eingegeben. – Runcible

+0

@Runcible, Ah, das ist wahr. Ich habe das nicht bemerkt. Es tut uns leid. Ich werde versuchen, meine Antwort zu aktualisieren, um dieses Problem zu beheben. – strager

+0

@Runcible: sollten Sie für die Iteration, die den doStuff() - Aufruf fehlgeschlagen hat, den Aufruf von undoStuff() aufrufen? Natürlich hängt das vom Verhalten der Methoden ab, aber oft würden Sie nicht (dh, Sie rufen fclose() nicht für ein fehlgeschlagenes fopen() auf). –

1

Ok, ich auf einem Bein hier gehen werde ..

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

Vielleicht irre ich das - aber wenn es einen Fehler auf dem ersten Element gibt, scheint es, als ob die Iter-- in der zweiten Schleife außerhalb der Reichweite und etwas Schlechtes tun? – Runcible

+0

Ich denke, Sie haben zumindest Recht, Dekrementieren eines begin() Iterators kann undefiniert sein. Schade, es wird definitiv die hässliche Linie überschreiten, indem ich iter-- ersetzen muss mit if (iter! = My_list.begin()) iter--; :) –

0

Dies ist, was ich über Engineering nennen, aber es ist so viel Spaß

// 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

Es auf das, was Ihre doStuff() Funktion tut, hängt und wie wichtig Leistung in Ihrem Kontext ist. Wenn möglich, wäre es wahrscheinlich klarer (dh - einfacher für den Leser), an einer Kopie Ihres Vektors zu arbeiten, und nur wenn alles in Ordnung ist, tauschen Sie die Vektoren aus.

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 für die Verwendung von RAII! – Mankarse

+0

Ich würde einfach 'std :: vector's Kopierkonstruktor verwenden und sage' std :: vector workingCopy = myVector; '. Stilistisch würde ich lieber doStuff throw haben (angenommen, es handelt sich um eine Art komplexer Operation, ich bevorzuge Ausnahmen, wenn es irgendeine Art von tiefer Kette von Anrufen gibt, die an irgendeinem Punkt in der Mitte fehlschlagen könnten) oder sagen ( ) iter = workingCopy.begin(); iter! = workingCopy.end(); ++ iter) {if (! iter-> doStuff()) gibt false zurück; } return true; ' Und haben Sie eine eigene Funktion, die workingCopy als Referenz verwendet. Verwenden Sie den Rückgabewert, um zu bestimmen, ob getauscht werden soll. –

0

Dies kann mit einem reverse_iterator erfolgen:

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(); 
    } 
} 
Verwandte Themen