导致此问题有很多原因,常见的一个原因是将迭代器所指向的元素删除后迭代器失效,而本文要讨论的是另外一个原因:迭代器遍历容器时改变容器大小(capacity)而导致迭代器失效
结论:在进行迭代器遍历容器时,原来的容器不应该再继续压入任何元素了,否则迭代器可能失效。
原理部分
下面我们来以vector为例讨论一下迭代器失效背后的原理,以及什么时候会失效,什么时候正常。
先上结论:vector的迭代器失效的原因是,vector进行了改变容量大小(注意是capacity而非size)的操作,这个时候,会重新申请内存空间,并将原来的元素进行拷贝,这个时候指向原来位置的iterator仍旧指向原来的位置,而这时原来的位置的元素早已在vector扩容操作里被删除了,故此时iterator指向的是一个未知的值,故迭代器失效
下面给出几种情况的测试用例:
- 刚开始插入元素迭代器有效,之后失效(刚开始capacity大小为3,插入后不用扩容,故刚开始有效)
vector<int>vec_1{1};
vec_1.reserve(3);
for (auto itor = vec_1.begin(); itor != vec_1.end(); ++itor) {
vec_1.push_back(2);
}
- 刚开始容器大小为30,手动调用shrink_to_fit()函数失效(改变了容器大小)
vector<int>vec_2{ 1, 2, 3};
vec_2.reserve(30);
for (auto itor = vec_2.begin(); itor != vec_2.end(); ++itor) {
vec_2.shrink_to_fit();
}
- remove函数不改变容器的大小capacity不会失效
vector<int>vec_3{ 1, 2, 3 };
vec_3.reserve(30);
for (auto itor = vec_3.begin(); itor != vec_3.end(); ++itor) {
remove(vec_3.begin(), vec_3.end(),1);
}
注意,想要真正理解原理,必须了解vector的实现,下面附上与本问题相关的部分STL源码(GNU2.9版)
void push_back(const T& x) {
if (finish != end_of_storage) {
construct(finish, x);
++finish;
}
else
insert_aux(end(), x);
}
template <class T, class Alloc>
void vector<T, Alloc>::insert_aux(iterator position, const T& x) {
if (finish != end_of_storage) {
construct(finish, *(finish - 1));
++finish;
T x_copy = x;
copy_backward(position, finish - 2, finish - 1);
*position = x_copy;
}
else {
const size_type old_size = size();
const size_type len = old_size != 0 ? 2 * old_size : 1;
iterator new_start = data_allocator::allocate(len);
iterator new_finish = new_start;
__STL_TRY {
new_finish = uninitialized_copy(start, position, new_start);
construct(new_finish, x);
++new_finish;
new_finish = uninitialized_copy(position, finish, new_finish);
}
# ifdef __STL_USE_EXCEPTIONS
catch(...) {
destroy(new_start, new_finish);
data_allocator::deallocate(new_start, len);
throw;
}
# endif /* __STL_USE_EXCEPTIONS */
destroy(begin(), end());
deallocate();
start = new_start;
finish = new_finish;
end_of_storage = new_start + len;
}
}