2009-02-27 6 views
204

У меня есть код, который выглядит следующим образом:Можете ли вы удалить элементы из std :: list, итерации через него?

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

я хотел бы удалить неактивные элементы сразу после их обновления, Симметричного, чтобы избежать ходить снова список. Но если я добавлю прокомментированные строки, я получаю сообщение об ошибке, когда я доберусь до i++: «Список итераторов не увеличивается». Я попробовал несколько альтернатив, которые не увеличивались в инструкции for, но я не мог заставить ничего работать.

Каков наилучший способ удаления предметов, когда вы идете по std :: list?

ответ

227

Необходимо сначала увеличить итератор (с i ++), а затем удалить предыдущий элемент (например, используя возвращаемое значение из i ++). Вы можете изменить код на время цикла, как так:

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

совершенный, работал как шарм. – AShelly

+4

На самом деле, это не гарантирует работу. С «erase (i ++);», мы знаем только, что pre-incremented value передается в erase(), а i увеличивается до половины двоеточия, не обязательно перед вызовом erase(). "iterator prev = i ++; erase (prev);" обязательно будет работать, так как используется возвращаемое значение –

+38

Нет Джеймса, i увеличивается до вызова erase, и предыдущее значение передается функции. Аргументы функции должны быть полностью оценены до вызова функции. –

102

Вы хотите сделать:

i= items.erase(i); 

Это будет правильно обновлять итератор, чтобы указать на место после того, как итератор Вы удалили.

+64

Будьте предупреждены, t просто отбросьте этот код в свой цикл for. В противном случае вы будете пропускать элемент каждый раз, когда вы его удаляете. –

+1

не может он этого сделать; каждый раз, следуя его фрагменту кода, чтобы избежать пропусков? – enthusiasticgeek

+1

@enthusiasticgeek, что произойдет, если 'i == items.begin()'? – MSN

9

Использовать алгоритм std :: remove_if.

Edit: Работа с коллекциями должна быть как: 1. подготовить коллекцию. 2. сбор процесса.

Жизнь будет проще, если вы не будете смешивать эти шаги.

  1. std :: remove_if. или список :: remove_if (если вы знаете, что вы работаете со списком, а не с TCollection)
  2. станд :: for_each
+1

. Std :: list имеет функцию remove_if, которая более эффективна, чем алгоритм remove_if (и не требуется идиома «удалить-стереть»). –

2

Удаление аннулирует только итераторы, которые указывают на элементы, которые будут удалены.

Итак, в этом случае после удаления * i, я признан недействительным, и вы не можете увеличивать его.

Что вы можете сделать, это сначала сохранить итератор элемента, который нужно удалить, а затем увеличить итератор, а затем удалить сохраненный.

+2

Использование пост-приращения гораздо более элегантно. –

18

Вы должны сделать комбинацию ответа KRISTO и MSN-х:

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

Конечно, наиболее эффективным и SuperCool ® STL Сави, что было бы что-то вроде этого:

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

Я действительно рассматривал ваш метод SuperCool, поэтому я сомневался, что вызов remove_if не дает понять, что цель состоит в том, чтобы обрабатывать элементы, а не удалять их из списка активных. (Элементы не удаляются, потому что они только становятся неактивными, а не ненужными) – AShelly

+0

Я полагаю, что вы правы. С одной стороны, я склонен предлагать изменить имя «обновления», чтобы удалить неизвестность, но, по правде говоря, этот код очень похож на функторы, но он также не является чем-то непонятным. – Mike

+0

вы определяете 'iterator end', но никогда не используете его ... – JHBonarius

4

Альтернативой для версии цикла для ответа Кристо.

Вы теряете эффективность, вы идете назад, а затем переадресовываете снова при удалении, но в обмен на дополнительное увеличение итератора вы можете иметь итератор, объявленный в области цикла, и код, выглядящий немного чище. Выбор зависит от приоритетов момента.

Ответ был полностью вне времени, я знаю ...

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

Это то, что я тоже использовал. Но я не уверен, что он будет работать, если элемент, который нужно удалить, является первым элементом в контейнере. Для меня это работает, я думаю, но я не уверен, что он переносится на разных платформах. – trololo

+0

Я не делал «-1», однако список итераторов не может уменьшаться? По крайней мере, у меня есть утверждение из Visual Studio 2008. – milesma

+0

Пока связанный список реализуется как циклический двойной связанный список, имеющий узел head/stub (используемый как end() rbegin(), а когда пустым используется как begin() и rend() тоже) это сработает. Я не могу вспомнить, на какой платформе я использовал это, но он тоже работал для меня, поскольку реализация, названная выше, является наиболее распространенной реализацией для std :: list. Но, в любом случае, почти наверняка это было использование некоторого неопределенного (по стандарту C++) поведения, поэтому лучше не использовать его. –

-4

Я думаю, что у вас есть ошибка есть, я код так:

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

Вот пример использования цикла for, который перебирает список и приращения или источ- итератор в случае пункта удаляется во время обхода списка.

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

Если вы думаете о std::list, как очереди, то вы можете и епдиеее из очереди всех элементов, которые вы хотите сохранить, но только Dequeue (и не епдиеие) деталь вы хотите удалить. Вот пример, где я хочу, чтобы удалить 5 из списка, содержащего номера 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 теперь только цифры 1-4 и 6-10.

2

Вы можете написать

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

Вы можете написать эквивалентный код с std::list::remove_if, который является менее многословным и более явным

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

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

std::vector::erasestd::remove_if идиомы следует использовать, когда элементы вектор вместо список для сохранения конкретичности в O (n) - или в случае, если вы пишете общий код и элементы, может быть контейнером без эффективного способа удаления отдельных элементов (например, вектора)

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