迭代器失效就是说,对容器进行了一些操作后,先前的迭代器无法进行解引用操作去访问容器的元素。迭代器失效可能会造成程序崩溃,如下图
各类迭代器失效总括
Vector
- 如果插入元素导致vector达到最大容量,那么会重新分配内存并将老的元素拷贝到新的内存中。元素的地址都改变了,显然迭代器和引用都将失效;
- 如果插入元素没有引起重新分配内存,那插入元素的前面元素的迭代器和引用依然有效,但是插入元素以及它之后的所有元素的迭代器和引用将失效。因为它们往后挪了一个位置,地址发生了改变。
- 删除元素的前面元素的迭代器和引用依然有效,但是后面元素的迭代器和引用将失效。因为它们往前挪了一个位置,地址发生了改变。
deque
deque由于内部结构的复杂,迭代器失效的场景也会有些复杂。先看一下deque的内部大体实现:
deque会将新加入的元素加入到数据块里,如果当前的数据块满了,就会再申请一个数据块并将新元素插入到新数据块里。deque内部还有一个数据块指针数组,里面存放了指向这些数据块的指针。要想通过迭代器访问deque中的元素,首先迭代器需要包含一个指针指向数据块指针数组某个单元来访问数据块,其次还需要包含一个指针指向数据块里所要访问元素所在的具体位置。
下面具体分析deque迭代器失效的场景:
- 在队首或队尾插入元素,所有元素的迭代器都将失效,引用仍然有效。这点不太好理解,查阅了网上很多解释,有一种说法比较合理:
在队首或队尾插入元素,数据块里的其他元素不会发生挪动,所以其他元素的地址不变,引用仍然有效。但是如果插入时数据块已满,那么就要重新申请数据块。有了新的数据块,那当然也要新加一个指针指向它,并添加到数据块指针数组。而数据块指针数组也是有最大容量的,如果这个数组已达到最大容量,那么就会回到vector达到最大容量时的场景,重新申请一块更大的内存,把老的指针全部拷贝到新的内存里。那么显然,所有元素的迭代器里存放的数据块指针还指向老的非法地址,迭代器也就变成失效了。 - 在中间位置插入元素,所有元素的迭代器和引用都将失效。这个应该是所有元素挪动引起的。
- 删除队首或队尾元素,除被删的元素外,其他元素的迭代器和引用都依然有效;
- 删除中间元素,所有元素的迭代器和引用都将失效。
list
- 插入元素不会影响其他元素的迭代器。因为list使用了不连续分配的内存。
- 除当前被删的元素,其他元素迭代器依然有效。
set/map
同list
unordered_set/unordered_map
- 如果插入元素后,容器的大小超过了它的最大容量,就会触发rehash,导致所有元素的迭代器失效,但是引用依然有效。
- 除被删的元素外,其他元素的迭代器依然有效。
code注意
经过erase(iter)之后的迭代器完全失效,该迭代器iter不能参与任何运算,包括iter++,*ite。erase的返回值通常是删除元素的下一个元素的迭代器,可以通过返回值避免操作失效的迭代器引发程序崩溃。
for (auto iterator = container.begin(); iterator != container.end();)
{
if (shouldDelete(*iterator))
iterator = container.erase(iterator); //erase删除元素,返回下一个迭代器
else
++iterator;
}
参考资料:
1.https://en.cppreference.com/w/cpp/container#Iterator_invalidation
2.https://stackoverflow.com/questions/1658956/c-deques-iterator-invalidated-after-push-front
3.https://stackoverflow.com/questions/913070/why-does-push-back-or-push-front-invalidate-a-deques-iterators
4.http://www.cplusplus.com/reference/unordered_set/unordered_set/insert/
5.https://www.cnblogs.com/fnlingnzb-learner/p/9300073.html