【问题标题】:Why does std::vector::insert invalidate all iterators after the insertion point为什么 std::vector::insert 在插入点之后使所有迭代器无效
【发布时间】:2013-07-27 17:43:12
【问题描述】:

insert-ing 到 std::vector 时,C++ 标准确保插入点之前的所有迭代器都保持有效,只要 capacity 没有用完(参见 [23.2.4.3/1] 或 std::vector iterator invalidation )。

在插入点之后不允许迭代器保持有效的原因是什么(如果容量没有用尽)?当然,它们随后会指向不同的元素,但(从std::vector 的假定实现)应该仍然可以使用这样的迭代器(例如取消引用它或增加它)。

【问题讨论】:

    标签: c++ stdvector


    【解决方案1】:

    您似乎认为“无效”迭代器只有在使用时会引发崩溃,但标准的定义更广泛。它包括迭代器仍然可以安全地被取消引用,但不再指向它预期指向的元素的可能性。 (这是观察到“未定义行为”并不意味着“您的程序将立即崩溃”的一个特例;它也可能意味着“您的程序将默默地计算错误的结果”甚至“什么都没有” 这个实现会出现明显错误。")

    使用erase 更容易说明为什么这是一个问题:

    #include <vector>
    #include <iostream>
    int main(void)
    {
        std::vector<int> a { 0, 1, 2, 3, 4, 4, 6 };
    
        for (auto p = a.begin(); p != a.end(); p++) // THIS IS WRONG
            if (*p == 4)
                a.erase(p);
    
        for (auto p = a.begin(); p != a.end(); p++)
            std::cout << ' ' << *p;
    
        std::cout << '\n';
    }
    

    在 C++ 的典型实现中,该程序不会崩溃,但它会打印 0 1 2 3 4 6,而不是像预期的那样打印 0 1 2 3 6,因为擦除第一个 4 无效 p - - 通过将其推进到第二个4

    您的 C++ 实现可能有一个特殊的“调试”模式,在该模式下,该程序在运行时确实崩溃。例如,使用 GCC 4.8:

    $ g++ -std=c++11 -W -Wall test.cc && ./a.out
     0 1 2 3 4 6
    

    但是

    $ g++ -std=c++11 -W -Wall -D_GLIBCXX_DEBUG test.cc && ./a.out
    /usr/include/c++/4.8/debug/safe_iterator.h:307:error: attempt to increment 
        a singular iterator.
    
    Objects involved in the operation:
    iterator "this" @ 0x0x7fff5d659470 {
    type = N11__gnu_debug14_Safe_iteratorIN9__gnu_cxx17__normal_iteratorIPiNSt9__cxx19986vectorIiSaIiEEEEENSt7__debug6vectorIiS6_EEEE (mutable iterator);
      state = singular;
      references sequence with type `NSt7__debug6vectorIiSaIiEEE' @ 0x0x7fff5d659470
    }
    Aborted
    

    请理解该程序会引发未定义的行为无论哪种方式。只是未定义行为的后果在调试模式下更加戏剧化。

    【讨论】:

    • 这正是问题所在:您提供的代码是否符合标准的 C++(尽管它可能无法达到预期的效果)?或者更确切地说,为什么它不符合标准?不幸的是,我对 C++ 的印象表明你的代码应该有效地给出你展示的输出。
    • @TobiasBrüll 上面的代码执行未定义的行为。如果编译器在将您的银行信息通过电子邮件发送到澳大利亚后格式化您的硬盘驱动器,它将完全符合标准,尽管这不符合最佳编译器实践。标准绝对不能保证 Zack 得到的输出,但它是允许的。另一方面,编译器使用的std::vector 的特定实现可能会在实际实践中保证上述行为(正式地,在文档中(不太可能),或者因为暴露了std::vector 的源代码)。
    • @TobiasBrüll Yakk 是正确的。请参阅编辑以获取更多说明。
    【解决方案2】:

    迭代器可能引用不同的元素足以使它们失效。迭代器应该在其有效生命周期内引用 same 元素。

    您是对的,在实践中,如果您要取消引用此类迭代器,您可能不会遇到任何崩溃或鼻恶魔,但这并不意味着它有效。

    【讨论】:

    • 你说通过 C++ 标准取消引用这样的迭代器不会导致未定义的行为?
    • @TobiasBrüll 不,根据 C++ 标准,它确实会导致未定义(根据 C++ 标准)的行为。如果容量没有改变,最常见的未定义行为是,在实际实践中,您现在获得的元素与您的迭代器上次引用的偏移量相同。但是,与其添加关于“改变它们所指内容的迭代器”的措辞,这将限制所使用的实现并使标准更难阅读(呵呵),他们基本上说迭代器要么总是引用相同的东西,要么变得无效。因此,理论上编译器可以进行一些优化。
    • @TobiasBrüll:不,这仍然是未定义的行为。这并不意味着你会崩溃。
    【解决方案3】:

    向量是动态增长的,所以当你推入向量时,如果项目没有空间,则需要为其分配内存。标准要求vector 必须将其元素存储在连续内存中,因此在分配内存时,它必须足以存储所有现有元素以及新元素。

    向量本身不知道任何迭代器,因此无法将它们更新到新的元素存储中。因此,在重新分配内存后,迭代器就无效了。

    【讨论】:

    • 重新分配是另一个问题。我说的是剩余容量足够的情况。
    • 行为需要是确定性和一致的。一个典型的用例是不知道或不关心容量是否>使用。
    【解决方案4】:

    向量不知道存在哪些迭代器。然而,插入元素后元素的内存位置发生了变化。这意味着,如果迭代器保持有效,则需要更新迭代器以反映该更改。但是vector不能做这个更新,因为它不知道存在哪些迭代器。

    【讨论】:

      猜你喜欢
      • 2017-04-15
      • 2020-11-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-08-31
      • 2011-05-06
      • 2018-01-27
      相关资源
      最近更新 更多