Können Sie Elemente aus einer std::list entfernen, während Sie sie durchlaufen?

Lesezeit: 7 Minuten

Ich habe Code, der so aussieht:

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 möchte inaktive Elemente sofort nach dem Aktualisieren entfernen, um ein erneutes Durchlaufen der Liste zu vermeiden. Aber wenn ich die auskommentierten Zeilen hinzufüge, erhalte ich eine Fehlermeldung, wenn ich dazu komme i++: “Listen-Iterator nicht inkrementierbar”. Ich habe einige Alternativen ausprobiert, die in der for-Anweisung nicht erhöht wurden, aber ich konnte nichts zum Laufen bringen.

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

  • Ich habe keine Lösung gesehen, die auf Rückwärtsiteration basiert. Ich habe so einen gepostet.

    – sancho.s ReinstateMonicaCellio

    13. März 2019 um 4:53 Uhr

Konnen Sie Elemente aus einer stdlist entfernen wahrend Sie sie
Michael Kristofik

Sie müssen den Iterator zuerst inkrementieren (mit i++) und dann das vorherige Element entfernen (z. B. indem Sie den Rückgabewert von i++ verwenden). Sie können den Code wie folgt in eine 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;
    }
}

  • Tatsächlich funktioniert das garantiert nicht. Bei “erase(i++);” wissen wir nur, dass der vorinkrementierte Wert an erase() übergeben wird und das i vor dem Semikolon inkrementiert wird, nicht unbedingt vor dem Aufruf von erase(). “Iterator prev = i++; erase(prev);” funktioniert sicher, ebenso wie die Verwendung des Rückgabewerts

    – James Curran

    27. Februar 2009 um 20:03 Uhr

  • Nein, James, i wird inkrementiert, bevor erase aufgerufen wird, und der vorherige Wert wird an die Funktion übergeben. Die Argumente einer Funktion müssen vollständig ausgewertet werden, bevor die Funktion aufgerufen wird.

    – Brian Neal

    27. Februar 2009 um 20:07 Uhr

  • @ James Curran: Das ist nicht richtig. ALLE Argumente werden vollständig ausgewertet, bevor eine Funktion aufgerufen wird.

    – Martin York

    28. Februar 2009 um 0:59 Uhr

  • Martin York hat recht. Alle Argumente für einen Funktionsaufruf werden vollständig ausgewertet, bevor eine Funktion aufgerufen wird, keine Ausnahmen. So funktioniert Funktion einfach. Und es hat nichts mit Ihrem Beispiel foo.b(i++).c(i++) zu tun (das ohnehin undefiniert ist)

    – jalf

    28. Februar 2009 um 15:59 Uhr

  • Die alternative Verwendung i = items.erase(i) ist sicherer, weil es für eine Liste äquivalent ist, aber immer noch funktioniert, wenn jemand den Container in einen Vektor ändert. Bei einem Vektor verschiebt erase() alles nach links, um das Loch zu füllen. Wenn Sie versuchen, das letzte Element mit Code zu entfernen, der den Iterator nach dem Löschen erhöht, bewegt sich das Ende nach links und der Iterator nach rechts. Vergangenheit das Ende. Und dann stürzt du ab.

    – Eric Seppanen

    2. Mai 2012 um 23:55 Uhr

Du willst machen:

i= items.erase(i);

Dadurch wird der Iterator korrekt aktualisiert, sodass er auf die Position nach dem entfernten Iterator zeigt.

  • Seien Sie gewarnt, dass Sie diesen Code nicht einfach in Ihre for-Schleife einfügen können. Andernfalls überspringen Sie jedes Mal ein Element, wenn Sie eines entfernen.

    – Michael Kristofik

    27. Februar 2009 um 19:39 Uhr

  • kann er nicht ich–; jedes Mal, wenn er seinem Codestück folgt, um ein Überspringen zu vermeiden?

    – begeisterter Geek

    19. März 2012 um 15:58 Uhr

  • @enthusiasticgeek, was passiert wenn i==items.begin()?

    – MSN

    19. März 2012 um 18:36 Uhr

  • @enthusiasticgeek, an diesem Punkt solltest du es einfach tun i= items.erase(i);. Es ist die kanonische Form und kümmert sich bereits um all diese Details.

    – MSN

    20. März 2012 um 4:18 Uhr

  • Michael weist auf einen großen Fallstrick hin, ich musste mich gerade mit der gleichen Sache befassen. Die einfachste Methode, dies zu vermeiden, bestand darin, die for()-Schleife in eine while()-Schleife zu zerlegen und beim Inkrementieren vorsichtig zu sein

    – Anne Quinn

    14. Januar 2014 um 11:49 Uhr


Konnen Sie Elemente aus einer stdlist entfernen wahrend Sie sie
Mike

Sie müssen die Kombination aus Kristos Antwort und MSNs machen:

// 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 != 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);
    }
}

Das Effizienteste und SuperCool® STL-sicherste wäre natürlich so etwas:

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

  • Ich habe Ihre SuperCool-Methode in Betracht gezogen, mein Zögern war, dass der Aufruf von remove_if nicht klar macht, dass das Ziel darin besteht, die Elemente zu verarbeiten, anstatt sie aus der Liste der aktiven zu entfernen. (Die Elemente werden nicht gelöscht, weil sie nur inaktiv werden, nicht unnötig)

    – AShelly

    27. Februar 2009 um 22:40 Uhr

  • Ich nehme an, Sie haben Recht. Einerseits neige ich dazu, vorzuschlagen, 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

    2. März 2009 um 0:35 Uhr

  • Fairer Kommentar, reparieren Sie entweder die while-Schleife, um end zu verwenden, oder löschen Sie die nicht verwendete Definition.

    – Mike

    12. April 2017 um 21:49 Uhr

  • Randnotiz: std::not1 und std::mem_fun sind veraltet.

    – luizfls

    2. September 2021 um 21:12 Uhr

1647076808 893 Konnen Sie Elemente aus einer stdlist entfernen wahrend Sie sie
Mykola Golubjew

Verwenden std::remove_if Algorithmus.

Bearbeiten:

Die Arbeit mit Sammlungen sollte wie folgt aussehen:

  1. Sammlung vorbereiten.
  2. Sammlung verarbeiten.

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

  1. std::remove_if. oder list::remove_if (Wenn Sie wissen, dass Sie mit Liste arbeiten und nicht mit der TCollection )
  2. std::for_each

Ich habe es zusammengefasst, hier sind die drei Methoden mit Beispiel:

1. verwenden while Schleife

list<int> lst{4, 1, 2, 3, 5};

auto it = lst.begin();
while (it != lst.end()){
    if((*it % 2) == 1){
        it = lst.erase(it);// erase and go to next
    } else{
        ++it;  // go to next
    }
}

for(auto it:lst)cout<<it<<" ";
cout<<endl;  //4 2

2. verwenden remove_if Mitgliedsfunktion in der Liste:

list<int> lst{4, 1, 2, 3, 5};

lst.remove_if([](int a){return a % 2 == 1;});

for(auto it:lst)cout<<it<<" ";
cout<<endl;  //4 2

3. verwenden std::remove_if Funktion kombiniert mit erase Mitgliedsfunktion:

list<int> lst{4, 1, 2, 3, 5};

lst.erase(std::remove_if(lst.begin(), lst.end(), [](int a){
    return a % 2 == 1;
}), lst.end());

for(auto it:lst)cout<<it<<" ";
cout<<endl;  //4 2

4. verwenden for loop , sollte den Iterator aktualisieren:

list<int> lst{4, 1, 2, 3, 5};

for(auto it = lst.begin(); it != lst.end();++it){
    if ((*it % 2) == 1){
        it = lst.erase(it);  erase and go to next(erase will return the next iterator)
        --it;  // as it will be add again in for, so we go back one step
    }
}

for(auto it:lst)cout<<it<<" ";
cout<<endl;  //4 2 

  • In C++20 können Sie einfach verwenden std::erase_if(lst, pred). Es ist im Grunde dasselbe wie die Optionen 2 und 3, aber kürzer und funktioniert für jede Art von Container.

    – klott

    24. Mai 2021 um 21:27 Uhr


1647076808 747 Konnen Sie Elemente aus einer stdlist entfernen wahrend Sie sie
Rafael Gago

Die Alternative für die Loop-Version zu Kristos Antwort.

Sie verlieren etwas an Effizienz, Sie gehen beim Löschen rückwärts und dann wieder vorwärts, aber im Austausch für das zusätzliche Iteratorinkrement können Sie den Iterator im Schleifenbereich deklarieren und den Code etwas sauberer aussehen lassen. Was zu wählen ist, hängt von den Prioritäten des Augenblicks ab.

Die Antwort war total verspätet, 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);
    }
}

  • In C++20 können Sie einfach verwenden std::erase_if(lst, pred). Es ist im Grunde dasselbe wie die Optionen 2 und 3, aber kürzer und funktioniert für jede Art von Container.

    – klott

    24. Mai 2021 um 21:27 Uhr


1647076809 338 Konnen Sie Elemente aus einer stdlist entfernen wahrend Sie sie
David Kormack

Hier ist ein Beispiel mit a for Schleife, die die Liste iteriert und den Iterator inkrementiert oder erneut validiert, falls ein Element während des Durchlaufens der Liste entfernt wird.

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

993250cookie-checkKönnen Sie Elemente aus einer std::list entfernen, während Sie sie durchlaufen?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy