迭代 std::list 时擦除

Posted

技术标签:

【中文标题】迭代 std::list 时擦除【英文标题】:Erasing while iterating an std::list 【发布时间】:2013-04-29 00:43:32 【问题描述】:

如果我在 for 循环中使用 iterator 并且在迭代器的当前迭代中使用 erase,那么 for 循环应该继续正常并访问其余的 list 元素?

根据我的阅读,应该是这种情况,并且是listdequevector 的主要区别特征。出于我的目的,queue 可能有效,但我需要这种行为。

这是我正在考虑的循环:

    std::list<Sequence>::iterator iterator;
    iterator=m_concurrents.begin();
    for (;iterator!=m_concurrents.end();++iterator)
        if (iterator->passes())
            m_concurrents.erase(iterator);
        
    

【问题讨论】:

【参考方案1】:

编写该循环的惯用方式是:

for (auto i = list.begin(); i != list.end();) 
    if (condition)
        i = list.erase(i);
    else
        ++i;

您可以使用setmultisetmapmultimap 执行相同的操作。对于这些容器,您可以擦除元素而不影响任何迭代器对其他元素的有效性。像vectordeque 这样的其他容器就不那么友好了。对于那些容器,只有擦除迭代器之前的元素保持不变。这种差异仅仅是因为lists 将元素存储在单独分配的节点中。取出一个链接很容易。 vectors 是连续的,取出一个元素会将其后的所有元素移回一个位置。

您的循环已损坏,因为您在某些给定条件下擦除了i 处的元素。在该调用之后,i 不再是有效的迭代器。然后您的for 循环递增i,但i 无效。人间地狱随之而来。这就是为什么erase 在删除后将迭代器返回到元素的确切情况......所以你可以继续遍历list

你也可以使用list::remove_if:

list.remove_if([](auto& i)  return i > 10; );

在 lambda 中,如果要删除元素,则返回 true。在本例中,它将删除所有大于 10 的元素。

【讨论】:

在这个例子中,一旦迭代被擦除,您将停止迭代,因此不会迭代列表以可能查看其他元素。我对迭代整个列表并根据需要删除条目很感兴趣。据我了解,这对于list 是安全的,但对于其他容器则不然。这是我问题的核心,如果不清楚,请见谅。 这不适用于非 C++11 编译器。擦除成员方法仅在 C++03 中返回 void。将 i = list.erase(i) 更改为 list.erase(i++) 将修复它。 我很困惑,抱歉。仅当迭代器为 !cheesecake 时才递增迭代器。那么当它 cheesecake时它是如何递增的呢? 啊,我明白了。 ierase 返回时自动递增。我看到您正在使用 autoi 隐式指定为 iterator。仍然没有解释为什么我的 Q 中的循环“坏了”。看起来我在做类似的事情,只是风格不同。 也可以考虑list.erase(std::remove_if(list.begin(), list.end(), condition), list.end())【参考方案2】:
for (auto i = list.begin(); i != list.end(); ++i) 
    if (condition) 
        list.erase(i);
        --i;
    

【讨论】:

不要那样做。如果要删除第一个元素,它会“繁荣”。【参考方案3】:

如果只想使用for迭代器,可以这样使用,例如:

list<int> lst4, 1, 2, 3, 5;

for(auto it = lst.begin(); it != lst.end();++it)
    if ((*it % 2) == 1)
        it = lst.erase(it);  erase and go to next(erase will return the next iterator)
        --it;  // as it will be add again in for, so we go back one step
    


for(auto it:lst)cout<<it<<" ";
cout<<endl;  //4 2 

但是while迭代器中的擦除会更清晰:

list<int> lst4, 1, 2, 3, 5;

auto it = lst.begin();
while (it != lst.end())
    if((*it % 2) == 1)
        it = lst.erase(it);// erase and go to next
     else
        ++it;  // go to next
    


for(auto it:lst)cout<<it<<" ";
cout<<endl;  //4 2

你也可以使用成员函数remove_if

list<int> lst4, 1, 2, 3, 5;

lst.remove_if([](int a)return a % 2 == 1;);

for(auto it:lst)cout<<it<<" ";
cout<<endl;  //4 2

或将std::remove_iferase 功能结合使用:

list<int> lst4, 1, 2, 3, 5;

lst.erase(std::remove_if(lst.begin(), lst.end(), [](int a)
    return a % 2 == 1;
), lst.end());

for(auto it:lst)cout<<it<<" ";
cout<<endl;  //4 2

你也可以参考这个问题: Removing item from vector, while in C++11 range 'for' loop?

【讨论】:

以上是关于迭代 std::list 时擦除的主要内容,如果未能解决你的问题,请参考以下文章

如何在迭代boost :: intrusive :: list时擦除

如何使用迭代器擦除 std::list 模板

给定迭代器替换 std::list 对象

在迭代时从地图(或任何其他STL容器)中删除/删除内容

循环在 std::list 上为迭代的 std::erase 挂起

在使用矩阵进行计算时擦除 C++ 2d 向量行