C ++容器的迭代器失效规则是什么?
最好是摘要列表格式。
(注意:这本来是Stack Overflow的C ++ FAQ的一个条目。如果您想批评以这种形式提供FAQ的想法,则可以在开始所有这些操作的meta上进行发布。)该问题在C ++聊天室中进行监控,该问题最初是从FAQ想法开始的,所以提出这个想法的人很可能会读懂您的答案。)
C ++容器的迭代器失效规则是什么?
最好是摘要列表格式。
(注意:这本来是Stack Overflow的C ++ FAQ的一个条目。如果您想批评以这种形式提供FAQ的想法,则可以在开始所有这些操作的meta上进行发布。)该问题在C ++聊天室中进行监控,该问题最初是从FAQ想法开始的,所以提出这个想法的人很可能会读懂您的答案。)
Answers:
C ++ 17(所有参考均来自CPP17的最终工作草案-n4659)
序列容器
vector
:功能insert
,emplace_back
,emplace
,push_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]。
的insert
,emplace_front
,emplace_back
,emplace
,push_front
,push_back
功能是在本规则覆盖。
forward_list
:的重载均不insert_after
影响迭代器和引用的有效性[26.3.9.5/1]
array
:通常,数组的迭代器在数组的整个生命周期内都不会失效。但是,应该注意的是,在交换期间,迭代器将继续指向同一数组元素,因此将更改其值。
关联容器
All Associative Containers
:insert
和emplace
成员不得影响迭代器和对容器的引用的有效性[26.2.6 / 9]无序关联容器
All Unordered Associative Containers
:重新哈希将使迭代器无效,更改元素之间的顺序以及更改元素在其中出现的存储区,但不会使对元素的指针或引用无效。[26.2.7 / 9]
的insert
和emplace
成员不应影响到容器元素的引用的有效性,但可能违反所有迭代器在容器上。[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
:在擦除点或擦除点之后的函数erase
,pop_back
并使迭代器和引用无效。[26.3.11.5/3]
deque
:擦除操作的最后一个元素的擦除操作deque
只会使过去的迭代器以及所有迭代器和对已擦除元素的引用无效。擦除操作a的第一个元素deque
但不擦除最后一个元素的擦除操作仅会使迭代器和对已擦除元素的引用无效。既不擦除int的第一个元素也不最后一个元素的擦除操作deque
会使过去的迭代器和所有迭代器以及对的所有元素的引用无效deque
。[注意:pop_front
和pop_back
是擦除操作。—尾注] [26.3.8.4/4]
list
:仅使迭代器和对已删除元素的引用无效。[26.3.10.4/3]。这适用于erase
,pop_front
,pop_back
,clear
等功能。
remove
和remove_if
成员函数:清除所有在列表中的元件称为由列表迭代器i
的量,下列条件成立:*i == value
,pred(*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_list
:erase_after
只能使迭代器和对已删除元素的引用无效。[26.3.9.5/1]。
remove
和remove_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 Containers
:clear
会使引用a的元素的所有引用,指针和迭代器无效,并且可能使过去的迭代器无效(表87-序列容器要求)。但是,对于forward_list
,clear
不会使过去的迭代器无效。[26.3.9.5/32]
All Sequence Containers
:assign
使所有引用容器元素的引用,指针和迭代器无效。对于vector
和deque
,还会使过去的迭代器无效。(表87-序列容器要求)
关联容器
All Associative Containers
:erase
成员应仅使迭代器和对已删除元素的引用无效[26.2.6 / 9]
All Associative Containers
:extract
成员仅使删除元素的迭代器无效;指向被删除元素的指针和引用仍然有效[26.2.6 / 10]
容器适配器
stack
:从基础容器继承 queue
:从基础容器继承 priority_queue
:从基础容器继承 与迭代器失效有关的常规容器要求:
除非另外指定(显式指定或通过其他函数定义函数),否则调用容器成员函数或将容器作为参数传递给库函数均不得使对该容器内对象的迭代器或更改其值无效。[26.2.1 / 12]
没有swap()
函数会使引用被交换容器的元素的任何引用,指针或迭代器无效。[注意:end()迭代器未引用任何元素,因此可能无效。—尾注] [26.2.1 /(11.6)]
作为上述要求的示例:
transform
算法:op
和binary_op
函数不得使迭代器或子范围无效,或修改范围[28.6.4 / 1]中的元素
accumulate
算法:在[first,last]范围内,binary_op
不得修改元素或使迭代器或子范围无效[29.8.2 / 1]
reduce
算法:binary_op不得使迭代器或子范围无效,也不得修改[first,last]范围内的元素。[29.8.3 / 5]
等等...
std::string
吗?我认为这std::vector
与SSO 有所不同
string
未能通过上面列出的第二个一般要求。所以我没有包括在内。还尝试保持与以前的FAQ条目相同的模式。
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]除非另外指定(显式指定或通过其他函数定义函数),否则调用容器成员函数或将容器作为参数传递给库函数均不得使对该容器内对象的迭代器或更改其值无效。[23.1 / 11]
在C ++ 2003中,尚不清楚“结束”迭代器是否受上述规则约束。无论如何,您都应该假设它们是实际情况(实际就是这种情况)。
指针无效的规则与引用无效的规则相同。
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
:(不适用)除非另外指定(显式指定或通过其他函数定义函数),否则调用容器成员函数或将容器作为参数传递给库函数均不得使对该容器内对象的迭代器或更改其值无效。[23.2.1 / 11]
没有swap()函数会使引用 被交换容器的元素的任何引用,指针或迭代器无效。[注意:end()迭代器没有引用任何元素,因此它可能是无效的。—尾注] [23.2.1 / 10]
除了上述注意事项外swap()
,尚不清楚“最终”迭代器是否要遵守上面列出的每个容器规则;无论如何,您应该假设它们是。
vector
以及所有无序关联容器的支持reserve(n)
,保证至少在容器的大小增加到之前不会自动调整大小n
。应谨慎对待无序的关联容器,因为将来的建议将允许指定最小装载因子,这将允许insert
在足够的erase
操作将容器尺寸减小到最小以下之后进行重新哈希处理;该担保应被视为可能无效erase
。
swap()
,复制/移动分配时迭代器有效性的规则是什么?
std::basic_string
似乎没有被视为容器,并且在注释所适用的标准部分中肯定不是容器。不过,在哪里说不允许SSO(我知道COW在哪里)?
可能值得补充的是,只要所有插入操作都是通过此迭代器执行的,并且没有其他独立的迭代器无效事件发生,则可以保证任何类型的插入迭代器(std::back_insert_iterator
,,)保持有效。std::front_insert_iterator
std::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
将继续保持有效。
由于此问题获得了如此多的选票,并且成为了一种常见问题,所以我认为最好写一个单独的答案来提及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()
,否则您可能会对“ 过早 ”的重新分配感到惊讶。