std::list 反向迭代和擦除导致崩溃

std::list reverse iterating & erasing causes crash

本文关键字:擦除 崩溃 迭代 list std      更新时间:2023-10-16

我正在使用反向迭代器遍历 std::list,并使用插入它们时获得的前向迭代器从列表中删除一些元素。 示例程序如下所示。我读到从列表中删除元素不会使其他迭代器无效,除了引用已删除元素的迭代器。但是没有提到reverse_iterators,我的程序崩溃了。有人可以判断用法是否正确吗?

程序正在做的是将元素添加到列表中,存储其迭代器,反向迭代列表并使用其存储的迭代器删除列表中的唯一元素。

输出粘贴在代码示例下方。

#include <list>
#include <iostream>
using namespace std;
struct node
{
    int data;
    list<node*>::iterator iter;
} a;
int main()
{
    list<node*> l;
    a.data = 1;
    l.push_front( &a );
    a.iter = l.begin();
    list<node*>::reverse_iterator ri = l.rbegin();
    while ( ri != l.rend() )
    {
        cout << (*ri)->data << endl;
        list<node*>::reverse_iterator rj = ri;
        ++ri;
        if ( ri ==  l.rend() )
            cout << "before erase: reached end" << endl;
        l.erase((*rj)->iter);
        if ( ri ==  l.rend() )
            cout << "after erase : reached end" << endl;
        else
            cout << "after erase : Not reached end" << endl;
    }
}

输出

1
before erase: reached end
after erase : Not reached end
610568524
before erase : reached end
Segmentation fault

在 VS2010 下,它会在第一次循环传递时抛出异常:

 l.erase((*rj)->iter);
 if ( ri ==  l.rend() ) // exception

这应该让你大致了解发生了什么。你看,reverse_iterator只是标准迭代器的包装器。也就是说,你应该记住,它有base()返回底层迭代器的成员 - 你不必像在结构中那样将其存储在其他地方node

这是reverse_iteratoriterator的关系的一个很好的答案。在您的情况下,rbegin将基于begin迭代器。如果从列表中删除begin(这样做,因为它只有一个元素),则基于此iterator的所有reverse_iterator都将变为无效。记住这一点,你可以通过以下方式重写你的循环:

while ( ri != l.rend() )
{  
    cout << (*ri)->data << endl;
    list<node*>::reverse_iterator rj = ri;
    ++ri;
    if ( ri ==  l.rend() )
        cout << "before erase: reached end" << endl;
    // the actual underlying iterator has an offset of one
    list<node*>::iterator it = l.erase(--rj.base());
    ri = reverse_iterator<list<node*>::iterator>(it);
    // or just
    // ri = reverse_iterator<list<node*>::iterator>(l.erase(--rj.base()));
    if ( ri ==  l.rend() )
        cout << "after erase : reached end" << endl;
    else
        cout << "after erase : Not reached end" << endl;
}

反向迭代器(tpyly)不是一个唯一的类,而是普通迭代器上的适配器 - 它有一个迭代器作为成员进入列表,并使用它来执行自己的移动和取消引用。因此,当此列表迭代器失效时,反向迭代器也会失效。

我正在写一个答案来记录我的发现;如果你遇到这个问题,请尝试

SingerOfTheFall建议的方法,它就像一个魅力,例如:

        for(auto it=values.end();it!=values.begin();){
            if((*it).second.endPoint)
                break;
            values.erase((*(it--)).first);
        }

回到我对此的发现:

当我遇到程序挂起的问题时,我已经运行了valgrind检查,它删除了一些源自libstdc++的奇怪Invalid reads

Invalid read of size 8
    at 0x4EAA633: ??? (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.20)
    by 0x402FEC: std::_Rb_tree_iterator<std::pair<int const, GroupControl::Group::Entry> >::operator--() (stl_tree.h:218)

我怀疑在最后一个元素擦除后,rend()不会停止迭代器,并且++操作被困在循环中

您需要将擦除返回值存储到迭代器中。执行以下更改。

(*rj)->iter= l.erase((*rj)->iter);