提问人: 提问时间:6/22/2011 最后编辑:13 revs, 6 users 28%URL Rewriter Bot 更新时间:9/7/2021 访问量:174667
C++ 容器的迭代器失效规则
Iterator invalidation rules for C++ containers
答:
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
指针失效规则与引用失效规则相同。
评论
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 * B
z
B
容器适配器
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)
n
insert
erase
erase
评论
swap()
std::basic_string
可能值得补充的是,只要所有插入都通过该迭代器执行,并且没有发生其他独立的迭代器失效事件,任何类型的插入迭代器(、、)都可以保证保持有效。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
reserve()
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()
评论
C++17(所有参考文献均来自 CPP17 的最终工作草案 - n4659)
插入
序列容器
vector
:如果新大小大于旧容量,则函数 、 、 将导致重新分配。重新分配会使引用序列中元素的所有引用、指针和迭代器无效。如果没有重新分配 发生时,插入点之前的所有迭代器和引用仍然有效。[26.3.11.5/1]
对于函数,重新分配会使引用序列中元素的所有引用、指针和迭代器无效。在调用 之后发生的插入期间,不应发生重新分配,直到插入使向量的大小大于 的值为止。[26.3.11.3/6]insert
emplace_back
emplace
push_back
reserve
reserve()
capacity()
deque
:在 deque 中间插入会使所有迭代器和对 deque 元素的引用无效。在 deque 的任一端插入会使 deque 的所有迭代器失效,但对 deque 元素的引用的有效性没有影响。[26.3.8.4/1]list
:不影响迭代器和引用的有效性。如果引发异常,则不会产生任何影响。[26.3.10.4/1].
、、、 函数都包含在此规则中。insert
emplace_front
emplace_back
emplace
push_front
push_back
forward_list
:任何重载都不会影响迭代器和引用的有效性 [26.3.9.5/1]insert_after
array
:通常,数组的迭代器在数组的整个生命周期内永远不会失效。但是,应该注意的是,在交换期间,迭代器将继续指向相同的数组元素,从而更改其值。
关联容器
All Associative Containers
:和成员不应影响迭代器的有效性和对容器的引用 [26.2.6/9]insert
emplace
无序关联容器
All Unordered Associative Containers
:重新散列会使迭代器失效,更改元素之间的顺序,并更改元素出现在哪些存储桶中,但不会使指针或元素引用失效。[26.2.7/9]
and 成员不应影响对容器元素的引用的有效性,但可能会使容器的所有迭代器无效。[26.2.7/14]
如果 ,则 和 成员不应影响迭代器的有效性,其中 是插入操作之前容器中的元素数,是插入的元素数,是容器的桶计数,并且是容器的最大负载系数。[26.2.7/15]insert
emplace
insert
emplace
(N+n) <= z * B
N
n
B
z
All Unordered Associative Containers
:在合并操作(例如,)的情况下,引用传输元素的迭代器和引用的所有迭代器将失效,但对剩余元素的迭代器将保持有效。(表 91 — 无序关联容器要求)a.merge(a2)
a
a2
容器适配器
stack
:继承自底层容器queue
:继承自底层容器priority_queue
:继承自底层容器
擦 除
序列容器
vector
:在擦除点或擦除点之后使函数和迭代器和引用失效。[26.3.11.5/3]erase
pop_back
deque
:擦除最后一个元素的擦除操作仅使过去的迭代器以及所有迭代器和对擦除元素的引用无效。擦除 (dease) 操作擦除 (dease) 操作会擦除 (de) 元素的第一个元素,但不会擦除最后一个元素,该操作只会使迭代器和对已擦除元素的引用失效。既不擦除第一个元素也不擦除最后一个元素的擦除操作会使 过去结束的迭代器以及所有迭代器以及对 . [ 注:和 是擦除操作。[26.3.8.4/4]deque
deque
deque
deque
pop_front
pop_back
list
:仅使迭代器和对已擦除元素的引用无效。[26.3.10.4/3]. 这适用于 、 、 函数。
和成员函数:擦除列表迭代器引用的列表中的所有元素,这些元素具有以下条件:、.仅使迭代器和对已擦除元素的引用无效 [26.3.10.5/15]。
member function - 从迭代器引用的每组相等元素中擦除除第一个元素之外的所有元素,该范围(对于不带参数的 unique 版本)或(对于带有谓词参数的 unique 版本)所在的范围。仅使迭代器和对已擦除元素的引用无效。[26.3.10.5/19]erase
pop_front
pop_back
clear
remove
remove_if
i
*i == value
pred(*i) != false
unique
i
[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_after
remove
remove_if
*i == value
remove()
pred(*i)
remove_if()
unique
*i == *(i-1)
pred(*i, *(i - 1))
All Sequence Containers
:使引用 a 元素的所有引用、指针和迭代器无效,并可能使前期迭代器失效(表 87 — 序列容器要求)。但是 for 不会使过去的迭代器失效。[26.3.9.5/32]clear
forward_list
clear
All Sequence Containers
:使所有引用、指针和 引用容器元素的迭代器。for 和 也会使过去的迭代器失效。(表 87 — 序列容器要求)assign
vector
deque
关联容器
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()
作为上述要求的示例:
transform
algorithm: and 函数不得使迭代器或子范围失效,也不得修改范围中的元素 [28.6.4/1]op
binary_op
accumulate
algorithm:在 [first, last] 范围内,既不得修改元素,也不得使迭代器或子范围失效 [29.8.2/1]binary_op
reduce
算法:binary_op既不得使迭代器或子范围无效,也不得修改 [first, last] 范围内的元素。[29.8.3/5]
等等......
评论
std::string
std::vector
string
"invalidated" can mean "no longer points to what it used to", not just "may not point to any valid element"
这是来自 cppreference.com 的一个不错的汇总表:
在这里,插入是指将一个或多个元素添加到容器的任何方法,而擦除是指从容器中删除一个或多个元素的任何方法。
评论