关于c ++:迭代器失效规则

Iterator invalidation rules

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

最好是以摘要列表格式。

(注:这意味着是堆栈溢出的C++FAQ的一个条目。如果你想批评在这个表单中提供一个常见问题解答的想法,那么在meta上发布的开始所有这一切的地方就是这样做的地方。这个问题的答案是在C++聊天室中进行监控的,FAQ的想法一开始就出现了,所以你的答案很可能会被那些想出这个想法的人读到。


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

Unless otherwise specified (either
explicitly or by defining a function
in terms of other functions), invoking
a container member function or passing
a container as an argument to a
library function shall not invalidate
iterators to, or change the values of,
objects within that container.
[23.1/11]

注释2

在C++ 2003中还不清楚"结束"迭代器是否符合上述规则;无论如何,你应该假定它们是(实际上是这样的情况)。

注释3

指针无效化规则与引用无效化规则相同。


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]。如果插入不会导致容器大小超过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

Unless otherwise specified (either
explicitly or by defining a function
in terms of other functions), invoking
a container member function or passing
a container as an argument to a
library function shall not invalidate
iterators to, or change the values of,
objects within that container.
[23.2.1/11]

注释2

no swap() function invalidates any
references, pointers, or iterators
referring to the elements of the
containers being swapped. [ Note: The
end() iterator does not refer to any
element, so it may be invalidated.
—end note ] [23.2.1/10]

注释3

除了上述关于swap()的警告外,"end"迭代器是否受上述每个容器规则的约束尚不清楚;无论如何,您应该假设它们是。

注释4

vector和所有无序的关联容器都支持reserve(n),这保证了至少在容器大小增长到n之前不会自动调整大小。应谨慎对待无序的关联容器,因为未来的建议将允许指定最小负载系数,这将允许在足够的erase操作后在insert上发生再清洗,从而将容器大小减小到最小值以下;在erase操作后,该保证应被视为潜在无效。〔35〕。


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

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

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

例如,此代码

1
2
3
4
5
6
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将继续有效。


C++ 17(所有参考文献均来自CPP17-N465 9的最终工作草案)好的。插入

顺序容器好的。

  • vector:新容量大于旧容量时,insertemplace_backemplacepush_back等功能引起重新分配。重新分配会使引用序列中元素的所有引用、指针和迭代器失效。如果没有重新分配发生这种情况时,插入点之前的所有迭代器和引用都保持有效。[23.3.11.5/1 ]对于reserve函数,重新分配会使引用序列中元素的所有引用、指针和迭代器失效。在调用reserve()之后发生的插入期间,在插入将使向量的大小大于capacity()的值之前,不应发生重新分配。[23.3.113/ 6 ]好的。

  • deque:在deque中间插入将使所有迭代器和对deque元素的引用失效。deque两端的插入会使deque的所有迭代器失效,但不会影响deque元素引用的有效性。[23.3.84/ 1 ]好的。

  • list:不影响迭代器和引用的有效性。如果抛出异常,则没有任何效果。[23.3.104/1 ]。本规则涵盖了insertemplace_frontemplace_backemplacepush_frontpush_back功能。好的。

  • forward_listinsert_after的重载不影响迭代器和引用的有效性[26.3.9.5/1]好的。

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

关联式容器好的。

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

无序关联容器好的。

  • All Unordered Associative Containers:重新刷新将使迭代器失效,更改元素之间的顺序,以及buckets元素出现在其中的更改,但不会使指向元素或对元素的引用失效。[22.2.7/9 ]insertemplace成员不影响对容器元素的引用的有效性,但可能使容器的所有迭代器失效。[22.2.7/14 ]insertemplace成员不影响迭代器的有效性,如果(N+n) <= z * B,其中N是插入操作之前容器中的元素数,N是插入的元素数,B是容器的桶数,z是容器的最大值。负载因子[22.2.7/15 ]好的。

  • All Unordered Associative Containers案例:在一个merge操作(例如,a.merge(a2)),referring迭代器的元素的迭代器的转移和全referring到a将invalidated,但剩余的元素的迭代器在a2将保持有效。(table 91结合无序容器的要求)

    /好的。

集装箱adaptors

/好的。

  • stack:继承从标的容器
  • queue:继承从标的容器
  • priority_queue:继承从标的容器

erasure

序列容器

/好的。

  • vector:函数的erasepop_backinvalidate迭代器和证明人.或在点的erase。。。。。。。26.3.11.5 / [ 3 ]

    /好的。

  • deque:erase操作erases那最后的一元一dequeinvalidates只读的过去的end迭代器和迭代器的全和证明人的erased元素。安erase操作,erases第一元一deque但不是最后一个元素的迭代器invalidates只读和证明人的erased元素。安erase操作,erases不是第一个或最后一个元素的元素的一个dequeinvalidates在过去的end迭代器和迭代器的全和证明人的所有元素的deque。。。。。。。 [注:pop_frontpop_back是erase操作。端26.3.8.4 /注] [ 4 ]

    /好的。

  • list:invalidates只读的迭代器和证明人的erased元素。26.3.10.4 / [ 3 ]。这applies到erasepop_frontpop_backclear函数。 removeremove_if成员函数:erases所有元素在列表中所提到的列表迭代器的方法i下面的条件:*i == value扑克,pred(*i) != false。。。。。。。invalidates只读的迭代器和证明人的erased元素26.3.10.5 / [ 15 ]。 unique成员函数erases全但第一元从每一个连续集团等元素的提到的由i迭代器范围内[first + 1, last)方法(这*i == *(i-1)的版本与独特的好arguments)或pred(*i, *(i - 1))(设计独特的版本号的predicate argument holds)。invalidates只读的迭代器和证明人的erased元素。26.3.10.5 / [ 19 ]

    /好的。

  • forward_listerase_after应invalidate只读的迭代器和证明人的erased元素。26.3.9.5 / [ 1 ]。 removeremove_if成员函数erases所有元素在列表中所提到的列表迭代器(我用的扑克,下面的条件:*i == value(remove()),pred(*i)是真的(remove_if())。invalidates只读的迭代器和证明人的erased元素。26.3.9.6 / [ 12 ]。 unique成员函数erases全但第一元从每一组连续相等的元素的迭代器中提到的"我的范围中的第一个(+ 1),最后用这*i == *(i-1)(的版本与好的arguments)或pred(*i, *(i - 1))(的版本号的predicate argument holds)。invalidates只读的迭代器和证明人的erased元素。26.3.9.6 / [ 16 ]

    /好的。

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

  • All Sequence Containersassign使所有引用、指针和引用容器元素的迭代器。对于vectordeque,也会使结束迭代器失效。(表87-顺序容器要求)好的。

关联式容器好的。

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

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

容器适配器好的。

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

关于迭代器失效的一般容器要求:好的。

  • 除非另有规定(明确地或通过用其他函数定义函数),否则调用容器成员函数或将容器作为参数传递给库函数都不会使该容器中对象的迭代器失效或更改其值。[22.2.1/12 ]好的。

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

作为上述要求的示例:好的。

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

  • accumulate算法:在[第一,最后]范围内,binary_op既不能修改元素,也不能使迭代器或子范围失效[29.8.2/1]好的。

  • reduce算法:二进制运算既不能使迭代器或子范围无效,也不能修改范围[第一、最后]中的元素。[27.83/ 5 ]好的。

等等…好的。好啊。


由于这个问题吸引了这么多的选票,有点成为FAQ,我想最好写一个单独的答案来提及C++ 03和C++ 11之间的一个重要区别:EDCOX1 3的插入操作对迭代器和引用相对于EDCOX1、8、EDCX1、9、9的影响的有效性。上次投票的答案没有被注意到。

C++ 03:

Reallocation invalidates all the references, pointers, and iterators
referring to the elements in the sequence. It is guaranteed that no
reallocation takes place during insertions that happen after a call to
reserve() until the time when an insertion would make the size of the
vector greater than the size specified in the most recent call to
reserve().

C++ 11:

Reallocation invalidates all the references, pointers, and iterators
referring to the elements in the sequence. It is guaranteed that no
reallocation takes place during insertions that happen after a call to
reserve() until the time when an insertion would make the size of the
vector greater than the value of capacity().

因此,在C++ 03中,它不是"EDOCX1×10",如在另一个答案中所提到的,而是应该是"EDCOX1×11"。这是C++ 03不同于C++ 11的一个问题。在C++ 03中,一旦EDCOX1〔12〕导致向量的大小达到先前EDCOX1·8调用中指定的值(这可能小于当前EDCOX1 9),因为EDCOX1(8)可能导致比所要求的EDCOX1更大的9),任何后续EDCOX1(12)都可能导致重新分配并使所有迭代器失效。以及参考文献。在C++ 11中,这不会发生,并且您可以始终信任EDCOX1 OR 9,以确切地知道下一个重新分配不会在大小超过EDCOX1×9的情况下发生。

总之,如果你正在使用C++ 03向量,并且你想确保在执行插入时不会发生重新分配,那就是你以前传递给EDCOX1的参数的值8。你应该检查对EDCOX1 9调用的大小,而不是返回值,否则你可能会在"PrimTuu"上感到惊讶。重新分配。