迭代器失效规则


543

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

最好是摘要列表格式。

(注意:这本来是Stack Overflow的C ++ FAQ的一个条目。如果您想批评以这种形式提供FAQ的想法,则可以在开始所有这些操作的meta上进行发布。)该问题在C ++聊天室中进行监控,该问题最初是从FAQ想法开始的,所以提出这个想法的人很可能会读懂您的答案。)


答案应采用与您的答案相同的格式吗?
PW

@PW IMO,它是对称性的首选,但我不能强制它:P
轨道上的轻度竞赛

那c ++ 20呢?
Walter

1
@Walter还不存在;)
比赛(

引用来自Futurama的Leela的说法,这个问题来自愚蠢的时代,在我看来,应该保持开放。
RomanLuštrik19年

Answers:


110

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_list:的重载均不insert_after影响迭代器和引用的有效性[26.3.9.5/1]

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

关联容器

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

无序关联容器

  • All Unordered Associative Containers:重新哈希将使迭代器无效,更改元素之间的顺序以及更改元素在其中出现的存储区,但不会使对元素的指针或引用无效。[26.2.7 / 9]
    insertemplace成员不应影响到容器元素的引用的有效性,但可能违反所有迭代器在容器上。[26.2.7 / 14]
    如果insert和,则和emplace不会影响迭代器的有效性(N+n) <= z * B,其中,,其中N是插入操作之前容器中n的元素数量,B是插入的元素数量,是容器的存储区数量,并且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只会使过去的迭代器以及所有迭代器和对已擦除元素的引用无效。擦除操作a的第一个元素deque但不擦除最后一个元素的擦除操作仅会使迭代器和对已擦除元素的引用无效。既不擦除int的第一个元素也不最后一个元素的擦除操作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)(对于版本的唯一没有参数)或pred(*i, *(i - 1))(对于带有谓词参数的unique的版本)成立。仅使迭代器和对已擦除元素的引用无效。[26.3.10.5/19]

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

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

  • All Sequence Containersassign使所有引用容器元素的引用,指针和迭代器无效。对于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]

等等...


7
哦,PW,你是英雄!
Lightness Races in Orbit

2
@LightnessRacesinOrbit:尝试按照原始答案格式进行操作。:)
PW

1
我们还可以列出std::string吗?我认为这std::vector与SSO 有所不同
sp2danny

1
@ sp2danny:由于SSO,string未能通过上面列出的第二个一般要求。所以我没有包括在内。还尝试保持与以前的FAQ条目相同的模式。
PW

@LightnessRaceswithMonica谢谢大家的辛勤工作。我有一个问题使我困惑了好几天。在这些情况下,“无效”到底是什么意思?这是否代表"invalidated" can mean "no longer points to what it used to", not just "may not point to any valid element"答案中描述的@Marshall Clow ?还是仅表示两种情况中的一种?
瑞克

410

C ++ 03(来源:迭代器无效规则(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

除非另外指定(显式指定或通过其他函数定义函数),否则调用容器成员函数或将容器作为参数传递给库函数均不得使对该容器内对象的迭代器或更改其值无效。[23.1 / 11]

笔记2

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

注3

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


5
好主意,我只想指出一下:我认为关联容器可以折叠成一行,然后再添加另一行无序关联容器是值得的……尽管我不确定哈希表部分如何映射到插入/擦除上,您知道一种检查是否会触发重新哈希的方法吗?
Matthieu M.

1
IIRC,在某处规范指出,最终迭代器不是“针对该容器中的对象”的迭代器。我不知道这些保证在每种情况下如何寻找最终迭代器?
约翰尼斯·绍布

1
@MuhammadAnnaqeeb:坦白说,这个答案并不清楚,因为我走了一条捷径,但目的是说调整大小插入/擦除,就像需要重新分配一样,您可能会认为这与擦除相同然后重新插入所有受影响的元素。答案的这一部分肯定可以改进。
Lightness Races in Orbit 2015年

1
@Yakk:但是没有;参见引用的标准文本。看起来这在C ++ 11中已修复。:)
轻轨赛将于

1
@变形:双端队列将数据存储在不连续的块中。在开始或结尾处插入可能会分配一个新块,但它永远不会在前一个元素周围移动,因此指针保持有效。但是,如果分配了新块,则转到下一个/上一个元素的规则将更改,因此迭代器无效。
Nick Matteo

357

C ++ 11(来源:迭代器无效规则(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

除非另外指定(显式指定或通过其他函数定义函数),否则调用容器成员函数或将容器作为参数传递给库函数均不得使对该容器内对象的迭代器或更改其值无效。[23.2.1 / 11]

笔记2

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

注3

除了上述注意事项外swap()尚不清楚“最终”迭代器是否要遵守上面列出的每个容器规则;无论如何,您应该假设它们是。

注4

vector以及所有无序关联容器的支持reserve(n),保证至少在容器的大小增加到之前不会自动调整大小n。应谨慎对待无序的关联容器,因为将来的建议将允许指定最小装载因子,这将允许insert在足够的erase操作将容器尺寸减小到最小以下之后进行重新哈希处理;该担保应被视为可能无效erase


另外swap(),复制/移动分配时迭代器有效性的规则是什么?
再见了

@LightnessRacesinOrbit:像插入,擦除,调整大小和交换一样,复制/移动分配也是std :: vector的成员函数,因此我认为您也可以为其提供迭代器有效性规则。
再见了

@goodbyeera:您的意思是复制/移动分配元素?这不会影响任何迭代器。为什么会这样?您正在点击上面的注1
Lightness Races Orbit

1
我认为我犯了一个错误,因为std::basic_string似乎没有被视为容器,并且在注释所适用的标准部分中肯定不是容器。不过,在哪里说不允许SSO(我知道COW在哪里)?
Deduplicator 2014年

2
这些规则在C ++ 14中是否都一样?C ++ 17(据目前所知)?
einpoklum

40

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

例如,当您正在执行一系列插入操作成std::vector通过使用std::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();

即使向量“决定”在此过程的中间重新分配,也可以确保对向量执行有效的插入序列。迭代器it显然将变得无效,但it_ins将继续保持有效。


22

由于此问题获得了如此多的选票,并且成为了一种常见问题,所以我认为最好写一个单独的答案来提及C ++ 03和C ++ 11之间关于std::vector插入操作对C ++影响的重大区别。相对于reserve()和的迭代器和引用的有效性capacity(),但最受支持的答案未能引起注意。

C ++ 03:

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

C ++ 11:

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

因此在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中,一旦an insert()使向量的大小达到上一个reserve()调用中指定的值(capacity()由于a reserve()可能导致capacity()大于要求的值,则该值可能会小于当前调用的值),任何后续操作insert()都可能导致重新分配并使其无效所有的迭代器和引用。在C ++ 11中,这种情况不会发生,您总是可以放心capacity()地确定在大小超过之前不会进行下一次重新分配capacity()

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


14
但是,我会向任何对我这样做的编译器开枪,而当地的陪审团不会对我定罪。
Yakk-Adam Nevraumont 2014年

9
我没有“没注意到”这一点;这是C ++ 03中的编辑错误,已在C ++ 11中更正。没有主流的编译器利用该错误。
Lightness Races in Orbit

1
@Yakk我认为gcc在这种情况下已经使迭代器无效。
ShreevatsaR

2

这是来自cppreference.com的不错的摘要表:

在此处输入图片说明

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.