Frage Element aus Vektor entfernen, während in C ++ 11 Bereich für "Schleife"?


Ich habe einen Vektor von IInventory *, und ich bin durch die Liste mit C ++ 11 Bereich für, um Sachen mit jedem zu tun.

Nachdem ich einige Sachen mit einem gemacht habe, möchte ich es vielleicht aus der Liste entfernen und das Objekt löschen. Ich weiß, dass ich anrufen kann delete auf dem Zeiger jederzeit, um es aufzuräumen, aber was ist der richtige Weg, um es aus dem Vektor zu entfernen, während in dem Bereich for Schleife? Und wenn ich es aus der Liste entferne, wird meine Schleife ungültig gemacht?

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'...
}

76
2018-04-28 04:00


Ursprung


Antworten:


Nein, das kannst du nicht. Bereichsbasiert for ist, wenn Sie einmal auf jedes Element eines Containers zugreifen müssen.

Du solltest das Normale benutzen for Schleife oder einer seiner Cousins, wenn Sie den Container im Laufe der Zeit ändern müssen, auf ein Element mehr als einmal zugreifen oder auf andere Weise nichtlinear durch den Container iterieren.

Beispielsweise:

auto i = std::begin(inv);

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

71
2018-04-28 04:02



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

Eine bereichsbasierte for-Schleife ist nur syntaktische Zucker für "normale" Schleife mit Iteratoren, so dass das oben Gesagte gilt.

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

45
2018-04-28 04:34



Sie sollten den Vektor idealerweise 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 einem vector ein erase macht alle Iteratoren ungültig, die mit dem Element beginnen, das bis zu dem gelöscht wird end() Sie müssen 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 die b != v.end() test wie es ist. Wenn Sie versuchen, es wie folgt zu optimieren:

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

Sie werden in UB seit Ihrer laufen e wird nach dem ersten ungültig gemacht erase Anruf.


10
2018-04-28 04:31



Ist es eine strikte Anforderung, Elemente in dieser Schleife zu entfernen? Andernfalls könnten Sie die Zeiger, die Sie löschen möchten, auf NULL setzen und einen weiteren Durchlauf über den Vektor ausführen, um alle NULL-Zeiger 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);

5
2018-04-28 22:18



Entschuldigung für Necroposting und auch Entschuldigung, wenn meine C ++ - Expertise meine Antwort behindert, aber wenn Sie versuchen, jedes Element zu durchlaufen und mögliche Änderungen vorzunehmen (wie das Löschen eines Indexes), versuchen Sie es mit einer Backwords-for-Schleife.

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

//do stuff
//erase index x

}

Wenn der Index x gelöscht wird, ist die nächste Schleife für das Element "vor" der letzten Iteration. Ich hoffe wirklich, dass dies jemandem geholfen hat


1
2018-05-25 20:09



OK, ich bin spät dran, aber trotzdem: Sorry, nicht korrigieren was ich bisher gelesen habe - es ist möglich, brauchen Sie 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 zeigt, wird kein anderer Iterator ungültig, so dass wir dies tun können, ohne uns Sorgen machen zu müssen. Tatsächlich, std::remove_if (GCC-Implementierung zumindest) macht etwas sehr ähnliches (mit einer klassischen Schleife ...), nur nichts gelöscht und löscht nicht.

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


1
2018-03-15 12:29



Ich werde mit Beispiel zeigen, das folgende Beispiel entfernen Sie ungerade Elemente aus 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

Denken Sie daran, die Methode erase gibt den nächsten Iterator des übergebenen Iterators zurück.

Von Hier können wir eine generate-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;    
}

Siehe hier, um zu sehen, wie man es benutzt std::remove_if. https://en.cppreference.com/w/cpp/algorithm/remove


1
2017-07-29 01:56



Eine viel elegantere Lösung wäre es, zu wechseln std::list (vorausgesetzt, Sie benötigen keinen schnellen wahlfreien Zugriff).

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

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

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

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

Ich finde diese Syntax schmackhaft. Ich glaube nicht, dass ich jemals benutzen würde remove_if zum Std :: Vektoren -- es gibt so viel inv.begin() und inv.end() Lärm dort bist du wahrscheinlich besser dran mit ein Integer-Index-basiertes Löschen oder nur ein einfaches altes reguläres Iterator-basiertes Löschen (wie unten gezeigt). Aber Sie sollten nicht wirklich aus der Mitte eines entfernen std::vector jedenfalls sehr, also auf a wechseln list Für diesen Fall wird eine häufige Löschung der Mitte der Liste empfohlen.

Beachten Sie jedoch, dass ich keine Chance hatte, anzurufen delete auf der Widget*die wurden entfernt. 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 normale 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 dir die Länge nicht gefällt for( list<Widget*>::iterator iter = widgets.begin() ; ...kannst du benutzen

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

0
2018-04-30 02:50