Могу ли я удалить элементы из std:: list, когда я повторяю его?

Можно ли удалить элементы из std:: list, когда я повторяю его? Например, так:

std::list<int> lst;
//....
for (std::list<int> itr = lst.begin(); itr != lst.end(); itr++)
{
    if (*itr > 10)
         lst.remove(*itr);
}

? И почему?

Ответ 1

Правильный код выглядит следующим образом:

for (std::list<int>::iterator itr = lst.begin(); itr != lst.end(); /*nothing*/)
{
    if (*itr > 10)
        itr = lst.erase(itr);
    else
        ++itr;
}

Когда вы удаляете элемент из списка, вы можете аннулировать итератор (если он указывает на удаляемый элемент). Поэтому вам нужно удалить с помощью erase (который возвращает действительный итератор, указывающий на следующий элемент).

Еще лучшая идея будет использовать std::remove_if:

bool greater_than_10(int x)
{
    return x > 10;
}

lst.remove_if(greater_than_10);

Если ваш компилятор поддерживает lambdas, вы можете поставить его еще короче:

lst.remove_if([](int x){ return x > 10; });

(я не тестировал этот код, поскольку мой компилятор не так уж и нов, лямбда-функция, к счастью, украдена из ответа @John Dibling.)


Фактически, удаление из списка отменяет только итераторы, указывающие на удаляемый элемент. Остерегайтесь, однако, что другие контейнеры STL не имеют этого свойства.


Итак, короче говоря: вообще говоря, вы не должны удалять элементы из списка во время итерации через него, потому что удаление может привести к аннулированию итератора (и программа, возможно, произойдет сбой). Если вы, тем не менее, уверены, что элементы, которые вы удаляете, не являются значениями, на которые ссылается любой из итераторов, которые вы используете в момент удаления, вы можете удалить.

Остерегайтесь того, что для других контейнеров STL (например, векторов) ограничение еще более строгое: удаление из контейнера делает недействительными не только итераторы, указывающие на удаленный элемент, но, возможно, и другие итераторы! Поэтому удаление из этих контейнеров при повторении через них еще более проблематично.

Ответ 2

Нет. В примере код недействителен itr, вызывая поведение undefined. Но это сработало бы:

for (std::list<int>::iterator itr = lst.begin(); itr != lst.end(); )
{
    if (*itr > 10)
        itr = lst.erase(itr);
    else
        ++itr;
}

Ответ 3

Нет, вы не можете.

Но вы можете (и должны) использовать std::remove_if вместе с функтором, который говорит "больше 10", например:

#include <list>
#include <algorithm>


int main()
{
    std::list<int> lst;
    lst.push_back(1);
    lst.push_back(12);
    lst.push_back(1);
    //....
    lst.erase(std::remove_if(lst.begin(), lst.end(), std::bind2nd(std::greater<int>(), 10)), lst.end());
}

Другой, более общий способ сделать это - написать собственный собственный функтор. Вот функтор is_a_match, который возвращает true, если проверяемое значение больше 10. Вы можете переопределить operator(), чтобы вернуть true, чтобы соответствовать тому, что в вашем случае означает "соответствовать":

#include <list>
#include <algorithm>
#include <functional>

struct is_a_match : public std::unary_function<int, bool>
{
    is_a_match(int val) : val_(val) {};
    bool operator()(int victim) const { return victim > val_; }
private:
    int val_;
};

int main()
{
    std::list<int> lst;
    lst.push_back(1);
    lst.push_back(12);
    lst.push_back(1);
    //....
    lst.erase(std::remove_if(lst.begin(), lst.end(), is_a_match(10) ));
}

Если у вас есть преимущество с компилятором С++ 0x, вы также можете использовать lambdas, что позволяет избавиться от функтора и писать во многих случаях более выразительный код.

#include <list>
#include <algorithm>

int main()
{
    std::list<int> lst;
    lst.push_back(1);
    lst.push_back(12);
    lst.push_back(1);
    //....
    lst.erase(std::remove_if(lst.begin(), lst.end(), [](int v) {return v > 10;}));
}

Ответ 4

Я думаю, что вы можете, но вам нужно переназначить итератор после удаления элемента, который можно сделать с помощью метода erase вместо этого remove.

В противном случае это будет небезопасно и не должно быть сделано.

Ответ 5

См. http://www.cppreference.com/wiki/iterator/start для описания итераторов.

Несколько примечаний:

  • Вы должны использовать оператор pre-increment (++itr) вместо оператора post-increment (itr++)
  • Недействительность зависит от точной реализации итератора и связанного с ним сбора.