使用交换和弹出进行迭代时擦除向量中的元素

Posted

技术标签:

【中文标题】使用交换和弹出进行迭代时擦除向量中的元素【英文标题】:Erasing element in a vector while iterating using swap-and-pop 【发布时间】:2012-12-10 15:34:08 【问题描述】:

我想在迭代向量时擦除某些元素,但下面的代码导致“向量迭代器不可递增”断言失败。

for(auto iter=vec.begin(); iter!=vec.end(); ++iter) 
     
    if((*iter).isDead())
            
        std::swap(*iter, vec.back());//swap with the back       
        vec.pop_back();     //erase the element
     
 

这段代码有什么问题?

【问题讨论】:

寻找remove_if,如果返回isDead怎么办? 我不关注。预期的结果是什么?为什么? @NoSenseEtAl:在某些情况下,“交换和弹出”是对remove_if 的大规模优化,因此 IMO 值得知道如何正确地做到这一点。可以说这种情况在实践中很少见,但比较这两种情况,向量元素类型的移动/复制成本很高,并且只有一个死元素,vec[0] WTF,这个Q怎么太本地化了,Steve Jessop刚刚解释了为什么知道它是件好事……我猜关闭Q的人忙着关闭Q而无法阅读所有的cmets。 当然,这个问题非常重要,一点也不“过于本地化”。 【参考方案1】:

仅当您在该迭代中不删除任何元素时才应递增迭代器:

for(auto iter=vec.begin(); iter!=vec.end();) 
     
    if((*iter).isDead())
            
        std::swap(*iter, vec.back());//swap with the back       
        vec.pop_back();     //erase the element
     
    else
        ++iter;

或者更好的是,将整个循环替换为remove_if

vec.erase(std::remove_if(vec.begin(), vec.end(),
    std::bind(&ValueType::isDead, _1)), vec.end());

【讨论】:

但是remove_if不会把每个元素都往下撞,这样效率低吗? @kiwon: remove_if 可能效率较低,是的,因为它不必要地保留了保留元素的原始顺序。 @kiwon:两者都是O(n),但常数可能不同。 @kiwon:好的,你说得对,需要进行线性数量的移动。问题是标准在这一点上确实有些模糊。然而,很难想象一个不这样做的实现......就像 comparisons 的数量表示排序的复杂性一样,但实际上这意味着任何理智的实现都不会做多余的复制/移动。 @Dan:它应该最多只需要做n - k 在一个模糊合理的实现中移动。据我所知,该标准并不能保证这一点,所以如果您的库实现是由一个 numty 编写的,那么您可能会遇到这个和其他性能问题。相比之下,提问者的方法是3*k 移动(假设默认为swap),可能大于n - k,也可能小于。【参考方案2】:

您正在递增超过与当前元素交换的元素;如果那是最后一个元素,那么您刚刚删除了它并使您的迭代器无效。如果你没有擦除,你只想增加,使用类似的东西:

for(auto iter=vec.begin(); iter!=vec.end();) 
     
    if((*iter).isDead())
            
        std::swap(*iter, vec.back());//swap with the back       
        vec.pop_back();     //erase the element
     else 
        ++iter;
    
 

【讨论】:

vec.pop_back() 可能会使iter 无效,不是吗?

以上是关于使用交换和弹出进行迭代时擦除向量中的元素的主要内容,如果未能解决你的问题,请参考以下文章

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

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

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

通过指针擦除 STL 向量

“向量擦除迭代器超出范围”错误

标准向量性能/替代