it-swarm.com.de

Element aus dem Vektor entfernen, während sich der C++ 11-Bereich für die Schleife befindet?

Ich habe einen Vektor von IInventory * und durchläuft die Liste mit dem C++ 11-Bereich für, um mit jedem etwas zu tun.

Nachdem ich einiges mit einem erledigt habe, möchte ich es vielleicht aus der Liste entfernen und das Objekt löschen. Ich weiß, dass ich jederzeit delete auf dem Zeiger aufrufen kann, um ihn zu bereinigen, aber wie kann er aus dem Vektor entfernt werden, wenn er sich im Bereich for befindet? Und wenn ich es aus der Liste entferne, wird meine Schleife ungültig?

std::vector<IInventory*> inv;
inv.Push_back(new Foo());
inv.Push_back(new Bar());

for (IInventory* index : inv)
{
    // Do some stuff
    // OK, I decided I need to remove this object from 'inv'...
}
81
EddieV223

Nein, das kannst du nicht. Die bereichsbasierte for ist für den Fall, in dem Sie einmal auf jedes Element eines Containers zugreifen müssen.

Sie sollten die normale for-Schleife oder einen ihrer Verwandten verwenden, wenn Sie den Container während des Vorgangs ändern, auf ein Element mehrmals zugreifen oder auf andere Weise durch den Container auf andere Weise durchlaufen müssen.

Zum Beispiel:

auto i = std::begin(inv);

while (i != std::end(inv)) {
    // Do some stuff
    if (blah)
        i = inv.erase(i);
    else
        ++i;
}
75
Seth Carnegie

Jedes Mal, wenn ein Element aus dem Vektor entfernt wird, müssen Sie davon ausgehen, dass die Iteratoren bei oder nach dem gelöschten Element nicht mehr gültig sind, da jedes der nach dem gelöschten Element folgenden Elemente verschoben wird.

Eine bereichsbasierte for-Schleife ist nur syntaktischer Zucker für "normale" Schleifen unter Verwendung von Iteratoren.

Davon abgesehen, könnten Sie einfach:

inv.erase(
    std::remove_if(
        inv.begin(),
        inv.end(),
        [](IInventory* element) -> bool {
            // Do "some stuff", then return true if element should be removed.
            return true;
        }
    ),
    inv.end()
);
47

Sie sollten den Vektor im Idealfall nicht ändern, während Sie darüber iterieren. Verwenden Sie das Erase-Remove-Idiom. Wenn Sie dies tun, werden Sie wahrscheinlich auf einige Probleme stoßen. Da in einer vector eine erase alle Iteratoren ungültig macht, beginnend mit dem zu löschenden Element bis zur end(), müssen Sie sicherstellen, dass Ihre Iteratoren gültig bleiben, indem Sie Folgendes verwenden:

for (MyVector::iterator b = v.begin(); b != v.end();) { 
    if (foo) {
       b = v.erase( b ); // reseat iterator to a valid value post-erase
    else {
       ++b;
    }
}

Beachten Sie, dass Sie den b != v.end()-Test in der vorliegenden Form benötigen. Wenn Sie versuchen, es wie folgt zu optimieren:

for (MyVector::iterator b = v.begin(), e = v.end(); b != e;)

sie werden auf UB stoßen, da Ihre e nach dem ersten Aufruf von erase ungültig wird.

11
dirkgently

Ist es eine strikte Anforderung, Elemente in dieser Schleife zu entfernen? Andernfalls könnten Sie die zu löschenden Zeiger auf NULL setzen und den Vektor erneut übergehen, um alle Nullzeiger zu entfernen.

std::vector<IInventory*> inv;
inv.Push_back( new Foo() );
inv.Push_back( new Bar() );

for ( IInventory* &index : inv )
{
    // do some stuff
    // ok I decided I need to remove this object from inv...?
    if (do_delete_index)
    {
        delete index;
        index = NULL;
    }
}
std::remove(inv.begin(), inv.end(), NULL);
6
Yexo

Ich werde mit Beispiel zeigen, das folgende Beispiel entfernt ungerade Elemente aus dem Vektor:

void test_del_vector(){
    std::vector<int> vecInt{0, 1, 2, 3, 4, 5};

    //method 1
    for(auto it = vecInt.begin();it != vecInt.end();){
        if(*it % 2){// remove all the odds
            it = vecInt.erase(it);
        } else{
            ++it;
        }
    }

    // output all the remaining elements
    for(auto const& it:vecInt)std::cout<<it;
    std::cout<<std::endl;

    // recreate vecInt, and use method 2
    vecInt = {0, 1, 2, 3, 4, 5};
    //method 2
    for(auto it=std::begin(vecInt);it!=std::end(vecInt);){
        if (*it % 2){
            it = vecInt.erase(it);
        }else{
            ++it;
        }
    }

    // output all the remaining elements
    for(auto const& it:vecInt)std::cout<<it;
    std::cout<<std::endl;

    // recreate vecInt, and use method 3
    vecInt = {0, 1, 2, 3, 4, 5};
    //method 3
    vecInt.erase(std::remove_if(vecInt.begin(), vecInt.end(),
                 [](const int a){return a % 2;}),
                 vecInt.end());

    // output all the remaining elements
    for(auto const& it:vecInt)std::cout<<it;
    std::cout<<std::endl;

}

ausgabe aw unten:

024
024
024

Beachten Sie, dass die Methode erase den nächsten Iterator des übergebenen Iterators zurückgibt.

Von hier können wir eine generierte Methode verwenden:

template<class Container, class F>
void erase_where(Container& c, F&& f)
{
    c.erase(std::remove_if(c.begin(), c.end(),std::forward<F>(f)),
            c.end());
}

void test_del_vector(){
    std::vector<int> vecInt{0, 1, 2, 3, 4, 5};
    //method 4
    auto is_odd = [](int x){return x % 2;};
    erase_where(vecInt, is_odd);

    // output all the remaining elements
    for(auto const& it:vecInt)std::cout<<it;
    std::cout<<std::endl;    
}

Hier erfahren Sie, wie Sie std::remove_if. https://en.cppreference.com/w/cpp/algorithm/remove verwenden.

1
Jayhello

entschuldigung für Nekroposting und Entschuldigung, wenn meine C++ - Expertise meiner Antwort im Weg steht. Wenn Sie jedoch versuchen, jedes Element zu durchlaufen und mögliche Änderungen vorzunehmen (z. B. einen Index löschen), versuchen Sie es mit einem Backwords for-Loop. 

for(int x=vector.getsize(); x>0; x--){

//do stuff
//erase index x

}

wenn Sie den Index x löschen, befindet sich die nächste Schleife für das Element "vor" der letzten Iteration. Ich hoffe wirklich, dass dies jemandem geholfen hat

1
lilbigwill99

OK, ich bin spät dran, aber trotzdem: Entschuldigung, nicht richtig, was ich bisher gelesen habe - es ist ist möglich, Sie brauchen nur zwei Iteratoren:

std::vector<IInventory*>::iterator current = inv.begin();
for (IInventory* index : inv)
{
    if(/* ... */)
    {
        delete index;
    }
    else
    {
        *current++ = index;
    }
}
inv.erase(current, inv.end());

Wenn Sie nur den Wert ändern, auf den ein Iterator verweist, wird kein anderer Iterator ungültig, sodass wir dies ohne Sorgen tun können. Tatsächlich macht std::remove_if (zumindest bei gcc) etwas sehr ähnliches (unter Verwendung einer klassischen Schleife ...), löscht einfach nichts und löscht nicht.

Beachten Sie jedoch, dass dies nicht fadensicher (!) Ist - dies gilt jedoch auch für einige der anderen Lösungen oben.

1
Aconcagua

Eine viel elegantere Lösung wäre der Wechsel zu std::list (vorausgesetzt, Sie benötigen keinen schnellen Direktzugriff).

list<Widget*> widgets ; // create and use this..

Sie können dann mit .remove_if und einem C++ - Funktionscode in einer Zeile löschen:

widgets.remove_if( []( Widget*w ){ return w->isExpired() ; } ) ;

Also schreibe ich hier nur einen Functor, der ein Argument akzeptiert (den Widget*). Der Rückgabewert ist die Bedingung, unter der ein Widget* aus der Liste entfernt werden soll.

Ich finde diese Syntax schmackhaft. Ich glaube nicht, dass ich remove_if für std :: Vectors verwenden würde - dort gibt es so viel inv.begin() und inv.end(), dass Sie wahrscheinlich besser mit einem auf Integer-Index basierenden delete oder einfach nur mit Ein einfaches altes, regelmäßiges Iterator-basiertes Löschen (wie unten gezeigt). Sie sollten jedoch ohnehin nicht wirklich aus der Mitte eines std::vector entfernt werden, so dass ein Wechsel zu einer list für diesen Fall eines häufigen Löschens der Mitte der Liste empfohlen wird.

Beachten Sie jedoch, dass ich keine Chance hatte, delete auf den Widget*s aufzurufen, die entfernt wurden. Um das zu tun, würde es so aussehen:

widgets.remove_if( []( Widget*w ){
  bool exp = w->isExpired() ;
  if( exp )  delete w ;       // delete the widget if it was expired
  return exp ;                // remove from widgets list if it was expired
} ) ;

Sie können auch eine reguläre Iterator-basierte Schleife verwenden:

//                                                              NO INCREMENT v
for( list<Widget*>::iterator iter = widgets.begin() ; iter != widgets.end() ; )
{
  if( (*iter)->isExpired() )
  {
    delete( *iter ) ;
    iter = widgets.erase( iter ) ; // _advances_ iter, so this loop is not infinite
  }
  else
    ++iter ;
}

Wenn Ihnen die Länge von for( list<Widget*>::iterator iter = widgets.begin() ; ... nicht gefällt, können Sie verwenden

for( auto iter = widgets.begin() ; ...
0
bobobobo

sie können den Iterator während der Schleifeniteration nicht löschen, da die Anzahl der Iteratoren nicht übereinstimmt und Sie nach einer gewissen Iteration einen ungültigen Iterator hätten.

Lösung: 1) Nehmen Sie die Kopie des ursprünglichen Vektors. 2) Iterieren Sie den Iterator mit dieser Kopie. 2) Führen Sie einige Aufgaben aus und löschen Sie sie vom ursprünglichen Vektor.

std::vector<IInventory*> inv;
inv.Push_back(new Foo());
inv.Push_back(new Bar());

std::vector<IInventory*> copyinv = inv;
iteratorCout = 0;
for (IInventory* index : copyinv)
{
    // Do some stuff
    // OK, I decided I need to remove this object from 'inv'...
    inv.erase(inv.begin() + iteratorCout);
    iteratorCout++;
}  
0
suraj kumar

Ich denke ich würde folgendes tun ...

for (auto itr = inv.begin(); itr != inv.end();)
{
   // Do some stuff
   if (OK, I decided I need to remove this object from 'inv')
      itr = inv.erase(itr);
   else
      ++itr;
}
0
ajpieri

Im Gegensatz zu diesem Thread-Titel würde ich zwei Pässe verwenden:

#include <algorithm>
#include <vector>

std::vector<IInventory*> inv;
inv.Push_back(new Foo());
inv.Push_back(new Bar());

std::vector<IInventory*> toDelete;

for (IInventory* index : inv)
{
    // Do some stuff
    if (deleteConditionTrue)
    {
        toDelete.Push_back(index);
    }
}

for (IInventory* index : toDelete)
{
    inv.erase(std::remove(inv.begin(), inv.end(), index), inv.end());
}
0
nikc