2009-02-27 5 views
204

Ich habe Code bekam die wie folgt aussieht:Können Sie Elemente aus einer std :: list entfernen, während Sie sie durchlaufen?

for (std::list<item*>::iterator i=items.begin();i!=items.end();i++) 
{ 
    bool isActive = (*i)->update(); 
    //if (!isActive) 
    // items.remove(*i); 
    //else 
     other_code_involving(*i); 
} 
items.remove_if(CheckItemNotActive); 

Ich mag inaktive Elemente, um sie aktualisieren unmittelbar nach entfernen würde, inorder um die Liste zu vermeiden, wieder zu Fuß. Aber wenn ich die auskommentierten Zeilen hinzufüge, erhalte ich einen Fehler, wenn ich zu i++ komme: "Liste Iterator nicht inkrementierbar". Ich habe einige Alternativen versucht, die in der for-Anweisung nicht erhöht wurden, aber ich konnte nichts zum arbeiten bekommen.

Was ist der beste Weg, um Elemente zu entfernen, während Sie eine std :: list laufen?

Antwort

227

Sie müssen zuerst den Iterator inkrementieren (mit i ++) und dann das vorherige Element entfernen (z. B. mit dem Rückgabewert von i ++). Sie können wie so um den Code zu einer while-Schleife ändern:

std::list<item*>::iterator i = items.begin(); 
while (i != items.end()) 
{ 
    bool isActive = (*i)->update(); 
    if (!isActive) 
    { 
     items.erase(i++); // alternatively, i = items.erase(i); 
    } 
    else 
    { 
     other_code_involving(*i); 
     ++i; 
    } 
} 
+0

perfekt, arbeitete wie ein Charme. – AShelly

+4

Das funktioniert nicht garantiert. Mit "Erase (i ++);" wissen wir nur, dass der vorinkrementierte Wert an Erase() übergeben wird und das i vor dem Semikolon erhöht wird, nicht unbedingt vor dem Aufruf von Erase(). Iterator prev = i ++; Löschen (prev); ist sicher zu arbeiten, wie ist der Rückgabewert –

+38

Nein James, ich wird inkrementiert vor dem Aufruf löschen, und der vorherige Wert wird an die Funktion übergeben. Die Argumente einer Funktion müssen vor dem Aufruf der Funktion vollständig ausgewertet werden. –

102

Sie tun mögen:

i= items.erase(i); 

das richtig den Iterator aktualisieren wird auf den Standort-zu-Punkt nach dem Iterator Sie entfernt.

+64

Seien Sie gewarnt, dass Sie können ' t einfach diesen Code in Ihre for-Schleife. Andernfalls überspringen Sie jedes Mal ein Element, wenn Sie eines entfernen. –

+1

kann er nicht ich--; Jedes Mal, wenn er seinem Code folgt, um ein Überspringen zu vermeiden? – enthusiasticgeek

+1

@enthusiasticgeek, was passiert, wenn 'i == items.begin()'? – MSN

9

Verwenden Sie den Algorithmus std :: remove_if.

Edit: Arbeit mit Sammlungen sollte wie: 1. Sammlung vorzubereiten. 2. Prozess Sammlung.

Das Leben wird einfacher, wenn Sie diese Schritte nicht mischen werden.

  1. Standard :: remove_if. oder Liste :: remove_if (wenn Sie wissen, dass Sie mit der Liste und nicht mit dem TCollection arbeiten)
  2. std :: for_each
+1

std :: list hat eine remove_if Member-Funktion, die effizienter ist als der remove_if-Algorithmus (und nicht erfordern das "Entfernen-Löschen" Idiom). –

2

Removal entkräftet nur die Iteratoren, die auf die Elemente verweisen, die entfernt werden.

Also in diesem Fall nach dem Entfernen von * i, ich bin ungültig und Sie können nicht erhöhen.

Sie können zuerst den Iterator des Elements, das entfernt werden soll, speichern, dann den Iterator inkrementieren und dann den gespeicherten Iterator entfernen.

+2

Post-Inkrement ist viel eleganter. –

18

Sie tun müssen, um die Kombination von Kristo Antwort und MSN: würde

// Note: Using the pre-increment operator is preferred for iterators because 
//  there can be a performance gain. 
// 
// Note: As long as you are iterating from beginning to end, without inserting 
//  along the way you can safely save end once; otherwise get it at the 
//  top of each loop. 

std::list< item * >::iterator iter = items.begin(); 
std::list< item * >::iterator end = items.end(); 

while (iter != items.end()) 
{ 
    item * pItem = *iter; 

    if (pItem->update() == true) 
    { 
     other_code_involving(pItem); 
     ++iter; 
    } 
    else 
    { 
     // BTW, who is deleting pItem, a.k.a. (*iter)? 
     iter = items.erase(iter); 
    } 
} 

Natürlich ist die effizienteste und Supercool ® STL savy Sache so etwas wie dieses:

// This implementation of update executes other_code_involving(Item *) if 
// this instance needs updating. 
// 
// This method returns true if this still needs future updates. 
// 
bool Item::update(void) 
{ 
    if (m_needsUpdates == true) 
    { 
     m_needsUpdates = other_code_involving(this); 
    } 

    return (m_needsUpdates); 
} 

// This call does everything the previous loop did!!! (Including the fact 
// that it isn't deleting the items that are erased!) 
items.remove_if(std::not1(std::mem_fun(&Item::update))); 
+0

Ich habe Ihre SuperCool-Methode in Betracht gezogen. Ich habe gezögert, dass der Aufruf von remove_if nicht klarstellt, dass das Ziel darin besteht, die Elemente zu verarbeiten, anstatt sie aus der Liste der aktiven Elemente zu entfernen. (Die Elemente werden nicht gelöscht, weil sie nur inaktiv werden, nicht unnötig) – AShelly

+0

Ich nehme an, Sie haben Recht. Auf der einen Seite möchte ich vorschlagen, den Namen von 'update' zu ändern, um die Unklarheit zu beseitigen, aber die Wahrheit ist, dass dieser Code mit den Funktoren ausgefallen ist, aber er ist auch alles andere als nicht obskur. – Mike

+0

definierst du 'iterator end' aber benutze es nie ... – JHBonarius

4

Die Alternative for-loop-Version zu Kristos Antwort.

Sie verlieren etwas Effizienz, Sie gehen zurück und dann wieder beim Löschen, aber im Austausch für die zusätzliche Iterator-Inkrement können Sie den Iterator im Loop-Bereich deklariert haben und der Code sieht ein bisschen sauberer. Was zu wählen ist, hängt von den Prioritäten des Augenblicks ab.

Die Antwort war völlig aus der Zeit, ich weiß ...

typedef std::list<item*>::iterator item_iterator; 

for(item_iterator i = items.begin(); i != items.end(); ++i) 
{ 
    bool isActive = (*i)->update(); 

    if (!isActive) 
    { 
     items.erase(i--); 
    } 
    else 
    { 
     other_code_involving(*i); 
    } 
} 
+1

Das habe ich auch benutzt. Aber ich bin mir nicht sicher, ob es funktioniert, wenn das zu entfernende Element das erste Element im Container ist. Für mich funktioniert es, denke ich, aber ich bin mir nicht sicher, ob es plattformübergreifend portabel ist. – trololo

+0

Ich habe nicht "-1", aber Liste Iterator kann nicht dekrementieren? Zumindest habe ich eine Bestätigung von Visual Studio 2008. – milesma

+0

Solange die verkettete Liste als eine zirkuläre doppelt verkettete Liste mit einem Kopf/Stub-Knoten implementiert ist (als end() rbegin() verwendet und wenn leer als begin() verwendet wird und rend() auch) das wird funktionieren. Ich kann mich nicht erinnern, in welcher Plattform ich diese verwendet habe, aber es hat auch für mich funktioniert, wie die oben genannte Implementierung die häufigste Implementierung für eine std :: list ist. Aber es ist fast sicher, dass dies einige nicht definierte (nach dem C++ - Standard) Verhalten ausnutzt, also besser nicht verwenden. –

-4

Ich glaube, Sie einen Fehler dort haben, Code, den ich auf diese Weise:

for (std::list<CAudioChannel *>::iterator itAudioChannel = audioChannels.begin(); 
      itAudioChannel != audioChannels.end();) 
{ 
    CAudioChannel *audioChannel = *itAudioChannel; 
    std::list<CAudioChannel *>::iterator itCurrentAudioChannel = itAudioChannel; 
    itAudioChannel++; 

    if (audioChannel->destroyMe) 
    { 
     audioChannels.erase(itCurrentAudioChannel); 
     delete audioChannel; 
     continue; 
    } 
    audioChannel->Mix(outBuffer, numSamples); 
} 
4

Hier ist ein Beispiel einer for-Schleife, die die Liste und Schritten oder Flugüber Iterator im Falle eines Artikels iteriert während des Durchlaufens der Liste entfernt werden.

for(auto i = items.begin(); i != items.end();) 
{ 
    if(bool isActive = (*i)->update()) 
    { 
     other_code_involving(*i); 
     ++i; 

    } 
    else 
    { 
     i = items.erase(i); 

    } 

} 

items.remove_if(CheckItemNotActive); 
2

Wenn Sie der std::list wie eine Schlange denken, dann können Sie aus der Warteschlange entfernt und enqueue alle Elemente, die Sie behalten möchten, aber nur dequeue (und enqueue nicht) das Element, das Sie entfernen möchten. Hier ist ein Beispiel, wo ich 5 aus einer Liste mit den Nummern 1-10 entfernen möge ...

std::list<int> myList; 

int size = myList.size(); // The size needs to be saved to iterate through the whole thing 

for (int i = 0; i < size; ++i) 
{ 
    int val = myList.back() 
    myList.pop_back() // dequeue 
    if (val != 5) 
    { 
     myList.push_front(val) // enqueue if not 5 
    } 
} 

myList wird nun nur haben Zahlen 1-4 und 6-10.

2

können Sie schreiben

std::list<item*>::iterator i = items.begin(); 
while (i != items.end()) 
{ 
    bool isActive = (*i)->update(); 
    if (!isActive) { 
     i = items.erase(i); 
    } else { 
     other_code_involving(*i); 
     i++; 
    } 
} 

Sie können mit std::list::remove_if entsprechenden Code schreiben, die weniger ausführlich ist und expliziter

items.remove_if([] (item*i) { 
    bool isActive = (*i)->update(); 
    if (!isActive) 
     return true; 

    other_code_involving(*i); 
    return false; 
}); 

Die std::vector::erasestd::remove_if Idiom verwendet werden soll, wenn Elemente ist ein Vektor statt eine Liste, um die Komplexität bei O (n) zu halten - oder für den Fall, dass Sie generischen Code und Elemente schreiben, könnte ein Container ohne effektive Möglichkeit sein, einzelne Elemente (wie einen Vektor) zu löschen

items.erase(std::remove_if(begin(items), end(items), [] (item*i) { 
    bool isActive = (*i)->update(); 
    if (!isActive) 
     return true; 

    other_code_involving(*i); 
    return false; 
})); 
Verwandte Themen