【问题标题】:Iterator invalidation rules for C++ containersC++ 容器的迭代器失效规则
【发布时间】:2017-10-05 23:23:57
【问题描述】:

C++ 容器的迭代器失效规则是什么?

注意:此问答是Stack Overflow's C++ FAQ 中的一个条目。有关问题本身的元讨论应发布在the Meta question that started all of this,而不是此处。)

【问题讨论】:

  • 答案是否应该与您的答案格式相同?
  • @P.W IMO 优先考虑对称性,但我无法强制执行:P

标签: c++ iterator c++-standard-library c++-faq


【解决方案1】:

这是来自cppreference.com 的漂亮汇总表:

这里,insertion是指将一个或多个元素添加到容器中的任何方法,erasure是指从容器中删除一个或多个元素的任何方法。

【讨论】:

    【解决方案2】:

    C++17(所有引用均来自CPP17的最终工作草案-n4659


    插入

    序列容器

    • vector:如果新容量大于旧容量,函数insertemplace_backemplacepush_back 会导致重新分配。重新分配使引用序列中元素的所有引用、指针和迭代器无效。如果没有重新分配 发生时,插入点之前的所有迭代器和引用仍然有效。 [26.3.11.5/1]
      对于reserve 函数,重新分配使所有引用序列中元素的引用、指针和迭代器无效。在调用reserve() 之后发生的插入过程中不会发生重新分配,直到插入会使向量的大小大于capacity() 的值。 [26.3.11.3/6]

    • deque:在双端队列中间插入会使所有迭代器和对双端队列元素的引用无效。在双端队列的任何一端插入都会使双端队列的所有迭代器无效,但不会影响对双端队列元素的引用的有效性。 [26.3.8.4/1]

    • list:不影响迭代器和引用的有效性。如果抛出异常,则没有任何影响。 [26.3.10.4/1]。
      insertemplace_frontemplace_backemplacepush_frontpush_back 函数都包含在此规则中。

    • forward_listinsert_after 的任何重载都不会影响迭代器和引用的有效性 [26.3.9.5/1]

    • array:As a rule,数组的迭代器在数组的整个生命周期内都不会失效。不过需要注意的是,在交换期间,迭代器会继续指向同一个数组元素,因此会改变它的值。

    关联容器

    • All Associative Containersinsertemplace 成员不应影响迭代器的有效性和对容器的引用 [26.2.6/9]

    无序关联容器

    • All Unordered Associative Containers:重新散列使迭代器无效,更改元素之间的顺序,并更改哪些桶元素出现在其中,但不会使指针或对元素的引用无效。 [26.2.7/9]
      insertemplace 成员不应影响对容器元素的引用的有效性,但可能会使容器的所有迭代器无效。 [26.2.7/14]
      如果(N+n) <= z * Binsertemplace 成员不应影响迭代器的有效性,其中N 是插入操作之前容器中的元素数,n 是插入的元素数,@ 987654355@ 是容器的桶数,z 是容器的最大负载因子。 [26.2.7/15]

    • All Unordered Associative Containers:在合并操作的情况下(例如,a.merge(a2)),引用被转移元素的迭代器和引用a 的所有迭代器将失效,但指向a2 中剩余元素的迭代器将保留有效的。 (表 91 - 无序关联容器要求)

    容器适配器

    • stack:继承自底层容器
    • queue:继承自底层容器
    • priority_queue:继承自底层容器

    擦除

    序列容器

    • vector:函数erasepop_back 在擦除点或擦除点之后使迭代器和引用无效。 [26.3.11.5/3]

    • deque:擦除deque 的最后一个元素的擦除操作仅使过去的迭代器和所有迭代器以及对被擦除元素的引用无效。擦除 deque 的第一个元素但不擦除最后一个元素的擦除操作只会使迭代器和对已擦除元素的引用无效。既不擦除deque 的第一个元素也不擦除最后一个元素的擦除操作会使过去的迭代器和所有迭代器以及对deque 的所有元素的引用无效。 [ 注意:pop_frontpop_back 是擦除操作。 ——尾注] [26.3.8.4/4]

    • list:仅使迭代器和对已擦除元素的引用无效。 [26.3.10.4/3]。这适用于 erasepop_frontpop_backclear 函数。
      removeremove_if 成员函数:擦除列表迭代器 i 引用的列表中的所有元素以下条件成立:*i == valuepred(*i) != false。仅使迭代器和对已擦除元素的引用 [26.3.10.5/15] 无效。
      unique 成员函数 - 从迭代器 i 在范围[first + 1, last),其中*i == *(i-1)(对于不带参数的unique版本)或pred(*i, *(i - 1))(对于带有谓词参数的unique版本)成立。仅使迭代器和对已擦除元素的引用无效。 [26.3.10.5/19]

    • forward_list: erase_after 只会使迭代器和对已擦除元素的引用无效。 [26.3.9.5/1].
      removeremove_if 成员函数 - 擦除列表迭代器 i 引用的列表中的所有元素,这些元素满足以下条件:*i == value(对于 remove()) , pred(*i) 为真(对于 remove_if())。仅使迭代器和对已擦除元素的引用无效。 [26.3.9.6/12].
      unique 成员函数 - 从迭代器 i 引用的每个连续的相等元素组中删除除第一个元素以外的所有元素,范围为 [first + 1, last),其中 @987654398 @(对于没有参数的版本)或pred(*i, *(i - 1))(对于带有谓词参数的版本)成立。仅使迭代器和对已擦除元素的引用无效。 [26.3.9.6/16]

    • All Sequence Containersclear 使所有引用 a 元素的引用、指针和迭代器失效,并可能使过去的迭代器失效(表 87 — 序列容器要求)。但是对于forward_listclear 不会使过去的迭代器无效。 [26.3.9.5/32]

    • All Sequence Containers: assign 使所有引用、指针和 引用容器元素的迭代器。对于vectordeque,也使过去的迭代器无效。 (表 87 - 序列容器要求)

    关联容器

    • All Associative Containerserase 成员应仅使迭代器和对已擦除元素的引用无效 [26.2.6/9]

    • All Associative Containersextract 成员仅使删除元素的迭代器无效;指向已移除元素的指针和引用仍然有效 [26.2.6/10]

    容器适配器

    • stack:继承自底层容器
    • queue: 继承自底层容器
    • priority_queue:继承自底层容器

    与迭代器失效相关的一般容器要求:

    • 除非另有说明(显式或通过根据其他函数定义函数),调用容器成员函数或将容器作为参数传递给库函数不应使对象的迭代器无效或更改对象的值在那个容器内。 [26.2.1/12]

    • 没有swap() 函数使任何引用被交换的容器元素的引用、指针或迭代器无效。 [ 注意: end() 迭代器不引用任何元素,因此它可能会失效。 ——尾注] [26.2.1/(11.6)]

    作为上述要求的例子:

    • transform 算法:opbinary_op 函数不应使迭代器或子范围无效,或修改范围 [28.6.4/1] 中的元素

    • accumulate 算法:在范围 [first, last] 内,binary_op 既不修改元素也不使迭代器或子范围无效 [29.8.2/1]

    • reduce 算法:binary_op 不应使迭代器或子范围无效,也不应修改范围 [first, last] 中的元素。 [29.8.3/5]

    等等……

    【讨论】:

    • @LightnessRacesinOrbit:尝试按照您的原始答案格式进行操作。 :)
    • 我们也可以列出std::string 吗?我认为由于 SSO,它与 std::vector 不同
    • @sp2danny:由于 SSO,string 不符合上面列出的第二个一般要求。所以我没有包括它。还试图坚持以前的常见问题条目的相同模式。
    • @LightnessRaceswithMonica 谢谢你们的辛勤工作。我有一个问题让我困惑了好几天。在这些上下文中,“无效”究竟意味着什么?这是否意味着"invalidated" can mean "no longer points to what it used to", not just "may not point to any valid element" 如@Marshall Clow 在此answer 中描述的那样?或者它只表示 2 个条件中的 1 个?
    • @Rick:推荐阅读:"What is iterator invalidation?"
    【解决方案3】:

    可能值得补充的是,只要所有插入都通过此迭代器执行,并且没有其他独立的迭代器无效,任何类型的插入迭代器(std::back_insert_iteratorstd::front_insert_iteratorstd::insert_iterator)都保证保持有效事件发生。

    例如,当您使用std::insert_iteratorstd::vector 执行一系列插入操作时,这些插入很可能会触发向量重新分配,这将使所有“指向”该向量的迭代器无效。但是,有问题的插入迭代器保证保持有效,即您可以安全地继续插入序列。完全不用担心触发向量重新分配。

    同样,这仅适用于通过插入迭代器本身执行的插入。如果容器上的某个独立操作触发了迭代器失效事件,则插入迭代器也按照一般规则失效。

    比如这段代码

    std::vector<int> v(10);
    std::vector<int>::iterator it = v.begin() + 5;
    std::insert_iterator<std::vector<int> > it_ins(v, it);
    
    for (unsigned n = 20; n > 0; --n)
      *it_ins++ = rand();
    

    保证对向量执行有效的插入序列,即使向量“决定”在此过程中间的某个位置重新分配。迭代器it 显然会失效,但it_ins 将继续保持有效。

    【讨论】:

      【解决方案4】:

      C++11(来源:Iterator Invalidation Rules (C++0x)


      插入

      序列容器

      • vector:插入点之前的所有迭代器和引用都不受影响,除非新容器大小大于之前的容量(在这种情况下,所有迭代器和引用都无效)[23.3.6.5/1]
      • deque:所有迭代器和引用都无效,除非插入的成员位于双端队列的末端(前面或后面)(在这种情况下,所有迭代器都无效,但对元素的引用不受影响)[23.3.3.4/1]
      • list:所有迭代器和引用不受影响 [23.3.5.4/1]
      • forward_list:所有迭代器和引用不受影响(适用于insert_after [23.3.4.5/1]
      • array(不适用)

      关联容器

      • [multi]{set,map}:所有迭代器和引用不受影响 [23.2.4/9]

      未排序的关联容器

      • unordered_[multi]{set,map}:当重新散列发生时,所有迭代器都失效,但引用不受影响 [23.2.5/8]。如果插入不会导致容器的大小超过z * B,则不会发生重新散列,其中z 是最大负载因子,B 是当前桶数。 [23.2.5/14]

      容器适配器

      • stack:继承自底层容器
      • queue:继承自底层容器
      • priority_queue:继承自底层容器

      擦除

      序列容器

      • vector:在擦除点或之后的每个迭代器和引用都无效 [23.3.6.5/3]
      • deque:擦除最后一个元素只会使迭代器和对被擦除元素的引用和过去的迭代器无效;擦除第一个元素只会使迭代器和对被擦除元素的引用无效;擦除任何其他元素会使所有迭代器和引用(包括过去的迭代器)无效 [23.3.3.4/4]
      • list:只有迭代器和对已擦除元素的引用无效 [23.3.5.4/3]
      • forward_list:只有迭代器和对已擦除元素的引用无效(适用于erase_after [23.3.4.5/1]
      • array(不适用)

      关联容器

      • [multi]{set,map}:只有迭代器和对已擦除元素的引用无效 [23.2.4/9]

      无序关联容器

      • unordered_[multi]{set,map}:只有迭代器和对已擦除元素的引用无效 [23.2.5/13]

      容器适配器

      • stack:继承自底层容器
      • queue:继承自底层容器
      • priority_queue:继承自底层容器

      调整大小

      • vector:根据插入/擦除 [23.3.6.5/12]
      • deque:根据插入/擦除 [23.3.3.3/3]
      • list:根据插入/擦除 [23.3.5.3/1]
      • forward_list:根据插入/擦除 [23.3.4.5/25]
      • array:(不适用)

      注 1

      除非另有说明(要么 显式地或通过定义一个函数 就其他功能而言),调用 容器成员函数或传递 一个容器作为 a 的参数 库函数不应失效 迭代器,或改变, 该容器内的对象。 [23.2.1/11]

      注2

      没有 swap() 函数会使任何 引用、指针或迭代器 参考元素 正在交换的容器。 [注: end() 迭代器 没有引用任何 元素,所以它可能会失效。 —尾注] [23.2.1/10]

      注3

      除了上述关于swap()it's not clear whether "end" iterators are subject to the above listed per-container rules的警告;无论如何,你应该假设它们是。

      注4

      vector 和所有无序关联容器 支持reserve(n),这保证至少在容器大小增长到n 之前不会发生自动调整大小。 无序关联容器应该小心,因为未来的提案将允许指定最小负载因子,这将允许在足够的erase 操作减少下面的容器大小后在insert 上发生重新散列最小值;在erase 之后,应将保证视为可能无效。

      【讨论】:

      • 除了swap(),复制/移动赋值时迭代器有效性的规则是什么?
      • @LightnessRacesinOrbit:与插入、擦除、调整大小和交换一样,复制/移动赋值也是 std::vector 的成员函数,所以我认为您也可以为它们提供迭代器有效性规则。
      • @goodbyeera:你的意思是复制/移动分配一个元素?这不会影响任何迭代器。为什么会呢?您正在点击上面的 注 1
      • 我想我犯了一个错误,因为std::basic_string 似乎没有算作容器,当然也不是该注释适用的标准部分中的容器。不过,它在哪里说不允许 SSO(我知道 COW 是)?
      • 这些规则在 C++14 中都一样吗? C++17(就目前所知)?
      【解决方案5】:

      由于这个问题吸引了如此多的选票并且有点成为常见问题解答,我想最好写一个单独的答案来提及 C++03 和 C++11 之间关于std::vector 的影响的一个显着差异对迭代器有效性的插入操作以及对reserve()capacity() 的引用,而最受好评的答案没有注意到。

      C++ 03:

      重新分配使所有引用、指针和迭代器无效 指序列中的元素。保证没有 重新分配发生在调用之后发生的插入期间 reserve() 直到插入使 向量大于最近调用中指定的大小 保留()

      C++11:

      重新分配使所有引用、指针和迭代器无效 指序列中的元素。保证没有 重新分配发生在调用之后发生的插入期间 reserve() 直到插入使 向量大于容量()的值

      所以在 C++03 中,它不是另一个答案中提到的“unless the new container size is greater than the previous capacity (in which case all iterators and references are invalidated)”,而是应该是“greater than the size specified in the most recent call to reserve()”。这是 C++03 与 C++11 不同的一件事。在 C++03 中,一旦 insert() 导致向量的大小达到在之前的 reserve() 调用中指定的值(这很可能小于当前的 capacity(),因为 reserve() 可能会导致更大的capacity() 比要求的),任何后续的 insert() 都可能导致重新分配并使所有迭代器和引用无效。在 C++11 中,这不会发生,您始终可以相信 capacity() 确定在大小超过 capacity() 之前不会发生下一次重新分配。

      总之,如果您使用的是 C++03 向量,并且您希望确保在执行插入时不会发生重新分配,那么您应该使用之前传递给 reserve() 的参数的值检查大小,而不是调用capacity() 的返回值,否则您可能会对“过早”重新分配感到惊讶。

      【讨论】:

      • 但是,我会射杀任何对我这样做的编译器,而且国内没有陪审团会判定我有罪。
      • 我没有“没有注意到”这一点;这是 C++03 中的一个编辑错误,在 C++11 中得到了纠正。没有主流编译器利用这个错误。
      • @Yakk 我认为 gcc 在这种情况下已经使迭代器无效。
      【解决方案6】:

      C++03(来源:Iterator Invalidation Rules (C++03)


      插入

      序列容器

      • vector:插入点之前的所有迭代器和引用都不受影响,除非新容器大小大于之前的容量(在这种情况下,所有迭代器和引用都无效)[23.2.4.3/1]
      • deque:所有迭代器和引用都无效,除非插入的成员位于双端队列的末端(前面或后面)(在这种情况下,所有迭代器都无效,但对元素的引用不受影响)[23.2.1.3/1]
      • list:所有迭代器和引用不受影响 [23.2.2.3/1]

      关联容器

      • [multi]{set,map}:所有迭代器和引用不受影响 [23.1.2/8]

      容器适配器

      • stack: 继承自底层容器
      • queue:继承自底层容器
      • priority_queue:继承自底层容器

      擦除

      序列容器

      • vector:擦除点之后的每个迭代器和引用都无效 [23.2.4.3/3]
      • deque:所有迭代器和引用都无效,除非被擦除的成员位于双端队列的末尾(前面或后面)(在这种情况下,只有迭代器和对被擦除成员的引用无效)[23.2.1.3/4]
      • list:只有迭代器和对已擦除元素的引用无效 [23.2.2.3/3]

      关联容器

      • [multi]{set,map}:只有迭代器和对已擦除元素的引用无效 [23.1.2/8]

      容器适配器

      • stack:继承自底层容器
      • queue:继承自底层容器
      • priority_queue:继承自底层容器

      调整大小

      • vector:根据插入/擦除 [23.2.4.2/6]
      • deque:根据插入/擦除 [23.2.1.2/1]
      • list:根据插入/擦除 [23.2.2.2/1]

      注 1

      除非另有说明(要么 显式地或通过定义一个函数 就其他功能而言),调用 容器成员函数或传递 一个容器作为 a 的参数 库函数不应失效 迭代器,或改变, 该容器内的对象。 [23.1/11]

      注2

      It's not clear in C++2003 whether "end" iterators are subject to the above rules;无论如何,您应该假设它们是(实际上就是这种情况)。

      注3

      指针失效规则与引用失效规则相同。

      【讨论】:

      • 好主意,只是说一下:我认为 associative 容器可以在一行中折叠在一起,然后添加另一行 无序关联...虽然我不确定如何在插入/擦除上映射重新散列部分,但您知道检查是否会触发重新散列的方法吗?
      • IIRC,规范中的某处说最终迭代器不是“该容器内对象”的迭代器。我想知道这些保证如何在每种情况下寻找结束迭代器?
      • @MuhammadAnnaqeeb:这个答案诚然没有说清楚,因为我走捷径,但目的是说调整大小 is 插入/擦除,就像需要重新分配,您可能认为这与擦除然后重新插入所有受影响的元素相同。答案的那一部分当然可以改进。
      • @Yakk:但事实并非如此;参见引用的标准文本。看起来这在 C++11 中是固定的。 :)
      • @metamorphosis:双端队列将数据存储在不连续的块中。在开头或结尾插入可能会分配一个新块,但它永远不会围绕先前的元素移动,因此指针仍然有效。但是,如果分配了新块,则转到下一个/上一个元素的规则会发生变化,因此迭代器会失效。
      猜你喜欢
      • 2011-09-20
      • 1970-01-01
      • 2016-10-26
      • 2017-04-11
      • 1970-01-01
      • 2023-01-13
      相关资源
      最近更新 更多