2009-02-27 13 views
204

Mam kod, który wygląda tak:Czy potrafisz usuwać elementy ze std :: list podczas ich iteracji?

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

chciałbym usunąć nieaktywnych elementów natychmiast po ich aktualizacji, inorder uniknąć znowu chodzenie listę. Ale jeśli dodaję skomentowane linie, dostaję błąd, gdy dojdę do i++: "Lista iteratorów nie jest przyrostowa". Próbowałem niektórych zastępców, które nie zwiększyły się w oświadczeniu for, ale nie mogłem dostać nic do pracy.

Jaki jest najlepszy sposób na usunięcie przedmiotów podczas przechodzenia przez std :: list?

Odpowiedz

227

Najpierw należy zwiększyć iterator (za pomocą i ++), a następnie usunąć poprzedni element (np. Za pomocą zwróconej wartości z i ++). Można zmienić kod do pętli while tak:

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

idealny, działał jak czar. – AShelly

+4

Właściwie to nie gwarantuje pracy. Dzięki "erase (i ++);" wiemy tylko, że wstępnie zwiększona wartość jest przekazywana do erase(), a i jest zwiększane przed średnikiem, niekoniecznie przed wywołaniem wymazywania(). "iterator prev = i ++; erase (prev);" na pewno zadziała, jak to jest w użyciu wartość zwracana –

+38

Nie James, i jest zwiększana przed wywołaniem kasowania, a poprzednia wartość jest przekazywana do funkcji. Argumenty funkcji muszą zostać w pełni ocenione przed wywołaniem funkcji. –

102

Chcesz zrobić:

i= items.erase(i); 

To będzie poprawnie zaktualizować iterator, aby wskazać lokalizację po iterator usuniętego.

+64

Ostrzegam, że możesz ' po prostu upuść ten kod do pętli for. W przeciwnym razie pominiesz element za każdym razem, gdy go usuniesz. –

+1

czyż nie może tego zrobić----; za każdym razem podążając za jego fragmentem, aby uniknąć przeskakiwania? – enthusiasticgeek

+1

@enthusiasticgeek, co się stanie, jeśli 'i == items.begin()'? – MSN

9

Użyj algorytmu std :: remove_if.

Edit: Praca ze zbiorami powinno być tak: 1. przygotować kolekcję. 2. proces zbierania.

Życie będzie łatwiejsze, jeśli nie będziesz mieszał tych kroków.

  1. std :: remove_if. lub lista :: remove_if (jeśli wiesz, że pracujesz z listy, a nie z TCollection)
  2. std :: for_each
+1

ma funkcję remove_if member, która jest bardziej wydajna niż algorytm remove_if (i nie wymagać idiomu "usuń-usuń"). –

2

Usuwanie unieważnia tylko iteratory które wskazują na elementy, które zostały usunięte.

W tym przypadku po usunięciu * i, i jest nieważny i nie można wykonać na nim inkrementacji.

Co można zrobić, to najpierw zapisać iterator elementu, który ma zostać usunięty, a następnie zwiększyć iterator, a następnie usunąć zapisany.

+2

Korzystanie z post-increment jest znacznie bardziej eleganckie. –

18

Trzeba zrobić kombinację odpowiedzi Kristo i MSN na:

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

Oczywiście, najbardziej wydajny i SuperCool ® STL Savy byłoby coś takiego:

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

Rozważyłem twoją metodę SuperCool, moje wahanie było takie, że wywołanie remove_if nie wyjaśnia, że ​​celem jest przetwarzanie elementów, zamiast usuwania ich z listy aktywnych. (Pozycje nie są usuwane, ponieważ stają się nieaktywne, niepotrzebne). – AShelly

+0

Przypuszczam, że masz rację. Z jednej strony jestem skłonny zasugerować zmianę nazwy "aktualizacji" w celu usunięcia niejasności, ale prawda jest taka, że ​​ten kod jest fantazyjny z funktorami, ale jest także czymś innym niż niezrozumiałym. – Mike

+0

definiujesz 'koniec iteratora', ale nigdy go nie używasz ... – JHBonarius

4

Alternatywa dla wersji pętli do odpowiedzi Kristo.

Tracisz trochę wydajności, cofasz się, a potem ponownie, gdy usuwasz, ale w zamian za dodatkowy przyrost iteratora możesz zadeklarować iterator w zakresie pętli, a kod wygląda nieco czystszy. Co wybrać zależy od priorytetów chwili.

Odpowiedź była całkowicie spóźniona, wiem ...

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

Tego też użyłem. Ale nie jestem pewien, czy to działa, jeśli element do usunięcia jest pierwszym elementem w kontenerze. Myślę, że dla mnie działa, ale nie jestem pewien, czy jest przenośny na różnych platformach. – trololo

+0

Nie zrobiłem "-1", jednak iterator listy nie może zmniejszać? Przynajmniej mam zapewnienie z Visual Studio 2008. – milesma

+0

Dopóki lista połączona jest zaimplementowana jako kołowo podwójnie połączona lista mająca węzeł główny/skrótowy (używany jako end() rbegin(), a gdy pusty używany jako begin() i rend() też) to zadziała. Nie pamiętam, na jakiej platformie tego używałem, ale działało to również dla mnie, ponieważ wspomniana wyżej implementacja jest najczęstszą implementacją dla std :: list. Ale tak czy owak, jest prawie pewne, że wykorzystywało to pewne niezdefiniowane (przez standard C++) zachowanie, więc lepiej go nie używać. –

-4

myślę, że masz tam błąd, że kod ten sposób:

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

Oto przykład przy użyciu for pętlę, która wykonuje iteracje listę i zwiększa lub odnawia iterator w przypadku pozycji są usuwane podczas przeglądania listy.

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

Jeśli myślisz o std::list jak kolejce, wtedy można dequeue i enqueue wszystkie elementy, które chcesz zachować, ale tylko rozkolejkowania (a nie enqueue) element, który chcesz usunąć. Oto przykład, gdzie chcę, aby usunąć 5 z listy zawierającej numery 1-10 ...

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 mają teraz tylko cyfry 1-4 i 6-10.

2

Możesz napisać

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++; 
    } 
} 

Możesz napisać równoważny kod z std::list::remove_if, który jest mniej gadatliwe i bardziej wyraźne

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

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

std::vector::erasestd::remove_if idiom powinny być stosowane, gdy przedmioty jest wektorem zamiast listę, która ma zachować spójność w punkcie O (n) - lub w przypadku pisania kodu ogólnego i elementy mogą być pojemnikami bez skutecznego sposobu wymazywania pojedynczych elementów (takich jak wektor).

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; 
})); 
Powiązane problemy