2010-11-23 9 views
13

Kann ich Elemente aus der std :: list entfernen, wenn ich darauf iteriere? Zum Beispiel so:Kann ich Elemente aus std :: list entfernen, wenn ich darauf iteriere?

std::list<int> lst; 
//.... 
for (std::list<int> itr = lst.begin(); itr != lst.end(); itr++) 
{ 
    if (*itr > 10) 
     lst.remove(*itr); 
} 

? Und warum?

+1

möglich Duplikat [Löschen Elemente aus einer STL-Liste] (http://stackoverflow.com/questions/501962/erasing-items-from-an-stl-list) – sth

+0

Ja, aber nicht auf diese Weise. –

+0

mögliches Duplikat von [Können Sie Elemente aus einer std :: list entfernen, während Sie sie durchlaufen?] (Http://stackoverflow.com/questions/596162/can-you-remove-elements-from-a-stdlist-while- Iterating-through-it) – AShelly

Antwort

29

Der richtige Code ist folgende:

for (std::list<int>::iterator itr = lst.begin(); itr != lst.end(); /*nothing*/) 
{ 
    if (*itr > 10) 
     itr = lst.erase(itr); 
    else 
     ++itr; 
} 

Wenn Sie ein Element aus der Liste löschen, den Iterator ungültig machen kann daher müssen Sie löschen erase (falls es zeigt auf das Element gelöscht werden.) (die einen gültigen Iterator zurückgibt, der auf das nächste Element zeigt).

noch bessere Idee wäre mit std::remove_if:

bool greater_than_10(int x) 
{ 
    return x > 10; 
} 

lst.remove_if(greater_than_10); 

Wenn Ihr Compiler lambdas unterstützt, können Sie es ausdrückte noch kürzer:

lst.remove_if([](int x){ return x > 10; }); 

(Ich habe diesen Code nicht testen, wie mein Compiler ist nicht so neu, die Lambda-Funktion wird dankenswerterweise aus @John Diblings Antwort gestohlen.)


Tatsächlich löscht das Löschen aus der Liste only the iterators pointing to the item being deleted. Beachten Sie jedoch, dass andere STL-Container diese Eigenschaft nicht haben.


Also, kurz gesagt: im Allgemeinen, sollten Sie nicht die Elemente aus der Liste löschen, während sie durch das Iterieren, weil die Löschung des Iterator ungültig machen kann (und das Programm wird möglicherweise Absturz). Wenn Sie jedoch absolut sicher sind, dass die Elemente, die Sie löschen, nicht die Werte sind, die von einem der Iteratoren referenziert werden, die Sie zum Zeitpunkt des Löschens verwenden, können Sie löschen. Beachten Sie, dass die Einschränkung für die anderen STL-Container (z. B. Vektoren) noch strenger ist: Löschen aus dem Container macht nicht nur Iteratoren ungültig, die auf das gelöschte Element zeigen, sondern möglicherweise auch andere Iteratoren! So ist das Löschen von diesen Containern während der Iteration noch problematischer.

+0

Ha. Wenn zwei SO-Leute nahezu identischen Beispielcode posten, muss das eine gute Idee sein. – aschepler

+0

@aschelper: in der Tat ;-) – Vlad

+0

Oh, ich gab ein schlechtes Beispiel. Ich möchte nur Methode entfernen (ich möchte alle Elemente mit bestimmten Wert löschen, wenn die bestimmte Bedingung). –

0

Ich denke, Sie können, aber Sie müssen den Iterator nach dem Entfernen des Elements neu zuweisen, die mit erase Methode statt remove durchgeführt werden kann.

Andernfalls ist es unsicher und sollte nicht getan werden.

8

Nein. Der Beispielcode macht itr ungültig, was zu undefiniertem Verhalten führt. Aber das würde funktionieren:

for (std::list<int>::iterator itr = lst.begin(); itr != lst.end();) 
{ 
    if (*itr > 10) 
     itr = lst.erase(itr); 
    else 
     ++itr; 
} 
+0

Vorinkrementieren des Iterators ist eine gute Idee; Ich habe meinen Code entsprechend geändert. Jetzt ist der Beispielcode genau der gleiche :) – Vlad

3

Nein, können Sie nicht.

Aber man kann (und sollte) verwenden std::remove_if zusammen mit einem Funktor, der „größer ist als 10`, wie dies sagt:

#include <list> 
#include <algorithm> 


int main() 
{ 
    std::list<int> lst; 
    lst.push_back(1); 
    lst.push_back(12); 
    lst.push_back(1); 
    //.... 
    lst.erase(std::remove_if(lst.begin(), lst.end(), std::bind2nd(std::greater<int>(), 10)), lst.end()); 
} 

Eine andere, allgemeinere Art und Weise, dies zu tun ist Ihre eigenen Funktor schreiben Hier ist ein Funktor is_a_match, der true zurückgibt, wenn der geprüfte Wert größer als 10 ist.Sie können operator() neu definieren true zurückkehren zu entsprechen, was es bedeutet, in Ihrem Fall „Match“:

#include <list> 
#include <algorithm> 
#include <functional> 

struct is_a_match : public std::unary_function<int, bool> 
{ 
    is_a_match(int val) : val_(val) {}; 
    bool operator()(int victim) const { return victim > val_; } 
private: 
    int val_; 
}; 

int main() 
{ 
    std::list<int> lst; 
    lst.push_back(1); 
    lst.push_back(12); 
    lst.push_back(1); 
    //.... 
    lst.erase(std::remove_if(lst.begin(), lst.end(), is_a_match(10))); 
} 

Wenn Sie den Nutzen eines C haben ++ 0x konformen Compiler, können Sie auch lambdas verwenden, das macht es möglich loswerden den Funktor zu bekommen und ausdruck Code in vielen Fällen

#include <list> 
#include <algorithm> 

int main() 
{ 
    std::list<int> lst; 
    lst.push_back(1); 
    lst.push_back(12); 
    lst.push_back(1); 
    //.... 
    lst.erase(std::remove_if(lst.begin(), lst.end(), [](int v) {return v > 10;})); 
} 
+0

Eigentlich hat 'list' seine eigene' remove_if' Mitgliedsfunktion: 'lst.remove_if (std :: bind2nd (std :: größer (), 10));' –

+0

@ Fred: Stimmt, aber das ist allgemeiner und lehrt, wie man das für andere Sammlungen als "liste" macht. Ich bevorzuge auch Nichtmitgliedsfunktionen, im Gegensatz zu Scott Meyers und zu einem ausgedehnten allgemeinen Gedanken. –

+0

@John: Einverstanden. Ich denke, dass 'list' einige Funktionen wie diese (und' sort' usw.) bietet, da sie für Listen besser geeignet sind. Es sieht aufgeräumter aus als das Löschen/Entfernen-Idiom auch. Aber ich schätze Ihren Standpunkt. –

0

Siehe http://www.cppreference.com/wiki/iterator/start für eine Beschreibung von Iteratoren schreiben.

Ein paar Anmerkungen:

  • Sie sollten die Pre-Inkrementoperator (++itr) anstelle des Post Inkrementoperator werden (itr++)
  • Invalidation hängt von der genauen Umsetzung der Iterator und seine zugehörige Sammlung.
Verwandte Themen