C++ 容器的迭代器失效规则

Iterator invalidation rules for C++ containers

提问人: 提问时间:6/22/2011 最后编辑:13 revs, 6 users 28%URL Rewriter Bot 更新时间:9/7/2021 访问量:174667

问:

这个问题的答案是社区的努力。编辑现有答案以改进此帖子。它目前不接受新的答案或交互。

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

注意:此问答是Stack Overflow的C++ FAQ中的条目。关于问题本身的元讨论应该发布在开始这一切的元问题上,而不是在这里。
C 迭代器 -标准库 C++ -FAQ

评论

0赞 P.W 1/1/2019
答案的格式应该与您的答案相同吗?
0赞 Lightness Races in Orbit 1/2/2019
@P.W IMO 对于对称性来说是首选,但我无法强制执行它:P

答:

447赞 Lightness Races in Orbit #1

C++03(源代码:迭代器失效规则 (C++03))


插入

序列容器

  • vector:插入点之前的所有迭代器和引用都不受影响,除非新的容器大小大于以前的容量(在这种情况下,所有迭代器和引用都将失效)[23.2.4.3/1]
  • deque:所有迭代器和引用都无效,除非插入的成员位于 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:所有迭代器和引用都无效,除非擦除的成员位于 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

除非另有说明(否则 显式或通过定义函数 就其他功能而言),调用 容器成员函数或传递 容器作为 库函数不得失效 迭代器或更改其值 该容器中的对象。 [23.1/11]

注2

在 C++2003 中不清楚“结束”迭代器是否受上述规则的约束;无论如何,您应该假设它们是(因为实践中就是这种情况)。

注3

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

评论

5赞 Matthieu M. 6/22/2011
好主意,只是想说:我认为关联容器可以折叠在一行中,然后添加另一行无序关联容器是值得的......虽然我不确定如何在插入/擦除时映射重新散列部分,但您知道检查是否会触发重新散列的方法吗?
1赞 Johannes Schaub - litb 6/22/2011
IIRC,规范中的某个地方说,最终迭代器不是“该容器中的对象”的迭代器。我想知道在每种情况下,这些保证如何寻找最终迭代器?
2赞 Lightness Races in Orbit 4/19/2015
@MuhammadAnnaqeeb:诚然,这个答案并不明确,因为我走了一条捷径,但目的是说调整大小插入/擦除,因为如果需要重新分配,您可能会认为这与擦除然后重新插入所有受影响的元素相同。答案的这一部分当然可以改进。
1赞 Lightness Races in Orbit 5/27/2015
@Yakk:但事实并非如此;参见引用的标准文本。看起来这在 C++11 中已修复。:)
1赞 Nick Matteo 4/17/2016
@metamorphosis:deque 将数据存储在不连续的块中。在开头或结尾插入可能会分配一个新块,但它永远不会绕过以前的元素,因此指针仍然有效。但是,如果分配了新块,则转到下一个/上一个元素的规则会发生变化,因此迭代器将失效。
376赞 Lightness Races in Orbit #2

C++11(源代码:迭代器失效规则 (C++0x))


插入

序列容器

  • vector:插入点之前的所有迭代器和引用都不受影响,除非新的容器大小大于以前的容量(在这种情况下,所有迭代器和引用都将失效)[23.3.6.5/1]
  • deque:所有迭代器和引用都无效,除非插入的成员位于 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]。如果插入不会导致容器的大小超过最大负载系数和当前存储桶数,则不会发生重新散列。[23.2.5/14]z * BzB

容器适配器

  • 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

除非另有说明(否则 显式或通过定义函数 就其他功能而言),调用 容器成员函数或传递 容器作为 库函数不得失效 迭代器或更改其值 该容器中的对象。 [23.2.1/11]

注2

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

注3

除了上面关于 的警告之外,尚不清楚 “end” 迭代器是否受上面列出的每个容器规则的约束;无论如何,你应该假设他们是。swap()

注4

vector并且支持所有无序关联容器,这保证至少在容器大小增长到 之前不会发生自动调整大小。对于无序关联容器应谨慎,因为未来的提案将允许规范最小负载系数,这将允许在足够多的操作将容器大小减小到最小值以下后进行重新散列;在 .reserve(n)ninserteraseerase

评论

0赞 goodbyeera 3/8/2014
此外,复制/移动分配时迭代器有效性的规则是什么?swap()
0赞 goodbyeera 3/8/2014
@LightnessRacesinOrbit:像插入、擦除、调整大小和交换一样,复制/移动分配也是 std::vector 的成员函数,所以我认为您也可以为它们提供迭代器有效性的规则。
0赞 Lightness Races in Orbit 3/8/2014
@goodbyeera:你的意思是复制/移动分配一个元素?这不会影响任何迭代器。为什么会这样?您正在点击上面的注释 1
1赞 Deduplicator 9/11/2014
我想我犯了一个错误,因为似乎不算作容器,当然也不算是注释适用的标准部分中的容器。不过,它在哪里说 SSO 是不允许的(我知道 COW 是)?std::basic_string
2赞 einpoklum 1/11/2016
这些规则在 C++14 中都是一样的吗?C++17(据现在所知)?
45赞 AnT stands with Russia #3

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

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

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

例如,以下代码

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();

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

23赞 neverhoodboy #4

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

C++ 03:

重新分配会使所有引用、指针和迭代器失效 引用序列中的元素。保证没有 重新分配发生在调用 reserve() 直到插入使 vector 大于最近一次调用中指定的大小 reserve() 中。

C++11:

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

所以在C++03中,它不是另一个答案中提到的“”,而是应该是“”。这是 C++03 与 C++11 不同的一件事。在 C++03 中,一旦 a 导致向量的大小达到上一次调用中指定的值(很可能小于当前值,因为 a 可能导致比请求的更大),任何后续都可能导致重新分配并使所有迭代器和引用无效。在 C++11 中,这不会发生,您始终可以确信下一次重新分配不会在大小过交之前发生。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()insert()reserve()capacity()reserve()capacity()insert()capacity()capacity()

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

评论

16赞 Yakk - Adam Nevraumont 5/2/2014
然而,我会枪毙任何对我这样做的编译者,而且这片土地上没有陪审团会给我定罪。
10赞 Lightness Races in Orbit 11/14/2014
我并没有“没有注意到”这一点;这是 C++03 中的一个编辑错误,在 C++11 中得到了纠正。没有主流编译器利用这个错误。
1赞 ShreevatsaR 11/25/2016
@Yakk我认为 gcc 在这种情况下已经使迭代器失效。
191赞 P.W #5

C++17(所有参考文献均来自 CPP17 的最终工作草案 - n4659)


插入

序列容器

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

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

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

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

  • array通常,数组的迭代器在数组的整个生命周期内永远不会失效。但是,应该注意的是,在交换期间,迭代器将继续指向相同的数组元素,从而更改其值。

关联容器

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

无序关联容器

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

  • All Unordered Associative Containers:在合并操作(例如,)的情况下,引用传输元素的迭代器和引用的所有迭代器将失效,但对剩余元素的迭代器将保持有效。(表 91 — 无序关联容器要求)a.merge(a2)aa2

容器适配器

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

擦 除

序列容器

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

  • deque:擦除最后一个元素的擦除操作仅使过去的迭代器以及所有迭代器和对擦除元素的引用无效。擦除 (dease) 操作擦除 (dease) 操作会擦除 (de) 元素的第一个元素,但不会擦除最后一个元素,该操作只会使迭代器和对已擦除元素的引用失效。既不擦除第一个元素也不擦除最后一个元素的擦除操作会使 过去结束的迭代器以及所有迭代器以及对 . [ 注:和 是擦除操作。[26.3.8.4/4]dequedequedequedequepop_frontpop_back

  • list:仅使迭代器和对已擦除元素的引用无效。[26.3.10.4/3]. 这适用于 、 、 函数。
    和成员函数:擦除列表迭代器引用的列表中的所有元素,这些元素具有以下条件:、.仅使迭代器和对已擦除元素的引用无效 [26.3.10.5/15]。
    member function - 从迭代器引用的每组相等元素中擦除除第一个元素之外的所有元素,该范围(对于不带参数的 unique 版本)或(对于带有谓词参数的 unique 版本)所在的范围。仅使迭代器和对已擦除元素的引用无效。[26.3.10.5/19]
    erasepop_frontpop_backclearremoveremove_ifi*i == valuepred(*i) != falseuniquei[first + 1, last)*i == *(i-1)pred(*i, *(i - 1))

  • forward_list:应仅使迭代器和对已擦除元素的引用无效。[26.3.9.5/1].
    和成员函数 - 擦除列表迭代器 i 引用的列表中的所有元素,这些元素具有以下条件:(for)、true (for)。仅使迭代器和对已擦除元素的引用无效。[26.3.9.6/12].
    member function - 从迭代器 i 引用的每组相等元素中擦除除第一个元素之外的所有元素,范围为 [first + 1, last),对于(对于没有参数的版本)或(对于具有谓词参数的版本)保持不变。仅使迭代器和对已擦除元素的引用无效。[26.3.9.6/16]
    erase_afterremoveremove_if*i == valueremove()pred(*i)remove_if()unique*i == *(i-1)pred(*i, *(i - 1))

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

  • All Sequence Containers:使所有引用、指针和 引用容器元素的迭代器。for 和 也会使过去的迭代器失效。(表 87 — 序列容器要求)assignvectordeque

关联容器

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

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

容器适配器

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

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

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

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

作为上述要求的示例:

  • transformalgorithm: and 函数不得使迭代器或子范围失效,也不得修改范围中的元素 [28.6.4/1]opbinary_op

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

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

等等......

评论

1赞 sp2danny 1/18/2019
我们也可以有一个列表吗?我认为这与SSO不同std::stringstd::vector
1赞 P.W 1/18/2019
@sp2danny:由于 SSO,无法满足上面列出的第二个常规要求。所以我没有包括它。还试图坚持与以前的FAQ条目相同的模式。string
1赞 Rick 12/10/2019
@LightnessRaceswithMonica 谢谢你们的辛勤工作。我有一个问题让我困惑了好几天。在这些上下文中,“无效”究竟意味着什么?这是否意味着正如@Marshall Clow 在此答案中描述的那样?或者它只表示 2 个条件中的 1 个?"invalidated" can mean "no longer points to what it used to", not just "may not point to any valid element"
0赞 Lightness Races in Orbit 12/10/2019
@Rick:推荐阅读:《什么是迭代器失效?
21赞 DarioP #6

这是来自 cppreference.com 的一个不错的汇总表:

enter image description here

在这里,插入是指将一个或多个元素添加到容器的任何方法,而擦除是指从容器中删除一个或多个元素的任何方法。