【问题标题】:Why std::vector iterator is invalidated after the erase() call?为什么 std::vector 迭代器在 erase() 调用后无效?
【发布时间】:2017-04-15 10:33:29
【问题描述】:

C++ 参考明确指出,在迭代器上调用std::vector::erase(it) 将使所有指向和之后的迭代器失效已擦除元素。 http://en.cppreference.com/w/cpp/container/vector/erase

我确实理解为什么这样的迭代器在erase 调用之后变得不可取消引用,但我很好奇为什么它们需要变得无效,哪些实现细节需要它?

例如,标准规定 std::vector 必须使用连续存储的元素来实现,而 elements can be accessed not only through iterators, but also using offsets on regular pointers to elements 因此这样的容器的迭代器可能会被实现为指针似乎是合乎逻辑的 - 但是指针如何变得无效?

【问题讨论】:

    标签: c++ c++11 vector stl iterator


    【解决方案1】:

    iterator 的概念思想所依据的原则之一如下:只要迭代器保持非别名、可解引用和不可修改,它就应该引用同一个实体.换句话说,多次取消引用同一个迭代器应该产生相同的值。使用迭代器的算法可能依赖于此。

    您提出的建议将导致迭代器“神奇地”改变它所引用的值,即使迭代器本身保持不变。这在迭代器的概念思想中是不可接受的。


    再想一想,我上面所说的显然是有缺陷的,因为我们总是可以对移动元素的向量应用一些修改操作(例如std::random_shuffle)。这样的操作不会使任何迭代器失效,但很容易改变迭代器引用的值。这与erase 触发的元素移动有何不同?不是。

    【讨论】:

    • 我明白了,-这正是我想要的,-谢谢@AnT!作为后续问题:这是否意味着减少或取消引用此类无效的迭代器仍然是合法的,或者总是被认为是未定义的行为?
    • @Feng Shu:取消引用或移动无效的迭代器始终是未定义的行为。可能会努力为std::vector 迭代器专门允许这样做,但这似乎不值得。迭代器概念的主要目的是充当算法和数据序列之间的抽象统一中介。出于这个原因,试图指定仅适用于非常狭窄的特定迭代器子集(向量迭代器)的东西对这个概念没有多大价值。
    • re std::random_shuffle 以及它与erase 的不同之处:可以说,洗牌只是改变容器中元素的值,但保持其几何形状不变,因此从这个意义上说,std::random_shuffle() 之后的所有迭代器仍然指向完全相同的容器元素(但这些元素改变了值)。而在erase() case 函数中更改容器几何形状,所以旧迭代器不再指向相同的逻辑元素,这就是迭代器无效的原因......
    • 当我读到这篇关于一些失效规则的帖子stackoverflow.com/questions/6438086/iterator-invalidation-rules 时,我也很难理解“无效”的实际含义。根据您的描述(对不起,我只是想确定一下),您是说如果迭代器不再“指向”相同的值,那么它也被认为是“无效的”?
    • 顺便说一句,你同意@Marshall Clow 的explanation 关于“无效”的含义,即"invalidated" can mean "no longer points to what it used to", not just "may not point to any valid element" 吗?
    【解决方案2】:

    “invalidated”可以表示“不再指向它以前的位置”,而不仅仅是“可能不指向任何有效元素”

    考虑(未编译的代码):

    vector<int> v = {0, 1, 2, 3, 4, 5};
    vector<int>::iterator iter = v.begin() + 3;  // "points to" 3
    assert(*iter == 3);
    v.erase(v.begin());
    

    此时,iter 已失效。它不再像以前那样“指向”相同的元素。

    【讨论】:

    • 嗯,我的印象是任何使用 invalidated 迭代器的尝试都会导致未定义的行为。你是在暗示情况并非如此吗?如果是这样,您能否指出任何说明这一点的参考资料?
    • 不,你是对的。一旦它们被无效,您将无法保证。它*iter可能具有值4。可能不会。它可能会使您的程序崩溃。
    【解决方案3】:

    std::vector 必须使用连续存储的元素来实现

    这就是原因。如果您擦除向量内的元素,则至少必须移动元素。你可以,不用调试保护:

    std::vector< int > test= {1,2,3,4,5,6,7};
    auto it= test.begin( ) + 2;
    test.erase( it );
    std::cout << *it << std::endl;
    

    它可能会打印“4”。但是没有保证。如果向量重新分配怎么办?如果你删除 test.begin( ) + 6 怎么办?如果你改变一个向量的大小,它就可以被移动。

    More Info

    【讨论】:

    • std::vector 不允许在 erase 上重新分配,直到它为空。 (显然,如果重新分配是可能的,它将使所有迭代器无效,包括那些指向已擦除迭代器之前的迭代器。) OP 似乎完全意识到这一点。
    • 但是被擦除元素之后的所有迭代器仍然会指向错误的元素,因为元素被移动了。
    • 嗨@AnT 我不知道这是一个规则。我一直认为如果大小发生变化,不相信向量会在哪里。谢谢。
    【解决方案4】:

    我根本看不出迭代器在我开始擦除元素时变得无效的原因。 vector::erase( ... ) 确实使用了赋值操作符,因此向量中的对象永远不会失效。如果我对自己的代码做同样的事情......

    template<typename T>
    void vector_erase( vector<T> &v, typename vector<T>::iterator first, typename vector<T>::iterator last )
    {
        typename vector<T>::iterator shiftOld,
                                     shiftNew;
        for( shiftOld = last, shiftNew = first; shiftOld != v.end(); ++shiftOld, ++shiftNew )
            *shiftNew = move( *shiftOld );
        v.resize( shiftNew - v.begin() );
    }
    

    ...迭代器在我切割向量之前一直有效。

    【讨论】:

    • 我不清楚争论的重点是什么。该标准说在擦除时或之后的迭代器变得无效。所以,是的,在擦除之前指向项目的迭代器仍然有效。您是在特别询问擦除开始时的那个吗?
    猜你喜欢
    • 2018-01-27
    • 1970-01-01
    • 2010-12-10
    • 1970-01-01
    • 2013-07-27
    • 2011-04-14
    • 1970-01-01
    • 1970-01-01
    • 2019-08-31
    相关资源
    最近更新 更多