C++迭代器失效
迭代器失效
你想握住的,可能已经散了
迭代器没报错,不代表还能用
在C++中,STL容器提供了一系列丰富的功能:从动态数组vector、链表list、到序列容器set/map等。正因为底层实现不同,导致在插入、删除、扩容、排序等操作,可能有以下行为:
- 迭代器失效
- 悬挂指针/引用
所以,本文会细节剖析迭代器失效的问题
什么是迭代器失效
迭代器:是C++中算法和容器之间的桥梁,主要目的是为了提供一种类指针的工具来操作容器。迭代器、指针、引用,在STL中本质是指向容器元素的句柄。
vector<int> nums = {1, 2, 3, 4, 5};// it就是一个迭代器,指向vector的第一个元素
auto it = nums.begin();
cout << *it << endl; // 输出1
但是,当容器内部结构变更的时候,如:
- 元素被删除/插入
- 容器进行扩容或者重排
- hash表的rehash
- 底层数据的迁移
这时候,句柄还可能被用户拿着,指向修改之前的内容,而继续操作这些原有的句柄,就会导致UB行为,最可能的情况就是段错误。
容器的迭代器的操作是否失效对比
容器 | 插入是否失效 | 删除 | 扩容 |
---|---|---|---|
vector | 插入的位置之后的迭代器全部失效 | 删除及其之后的全部失效 | 全部失效 |
deque | 插入之前/后的迭代器失效 | 删除及其之前/后的全部失效 | 是 |
list | 否 | 否(仅删除元素本身) | 否 |
map/set | 否 | 否仅删除元素本身) | 否 |
unordered_set/map | 是(可能造成rehash) | 是 | 是,全部失效 |
常用操作带来的失效问题
满大街都是这种文章
vector:
- erase:略
- push_back:略,扩容/尾迭代器失效
- insert
- 首先,和 push_back 一样,如果 vector 容量不够,insert会导致重新分配内存,所有迭代器就全军覆没了
- 即使没有重新分配内存,insert也会让插入位置及其后面的所有元素向后挪位置,这会使这些位置的迭代器全部"串位"
list:略
deque:略
map/set:略
unordered_map/set:略
earse和remove
+ remove只是重排,不是真正的删除元素(本质上是覆盖) + ease执行真正的删除逻辑代码示例:
std::remove(v.begin(), v.end(), val); // error
std::erase(std::remove(v.begin(), v.end(), val), v.end()); // ok
解决策略
方法一:返回值
> insert同erase >实际上,erase之后,会返回下一次的迭代器,那么有:
for (auto it = v.begin(); it != v.end(); it ++ )
{if (check()){it = v.erase(it);}
}
// insert的返回值
vector<int> nums2 = {1, 2, 3, 4};
auto it2 = nums2.begin();
it2 = nums2.insert(it2 + 2, 100); // it2现在指向新插入的100
cout << *it2 << endl; // 输出100
方法二:逆序删除
vector<int> nums = {1, 2, 3, 4, 5};
vector<int> toRemove;for (int i = 0; i < nums.size(); ++i) {if (nums[i] == 3) {toRemove.push_back(i);}
}// 从后往前删除(避免索引变化)
for (int i = toRemove.size() - 1; i >= 0; --i) {nums.erase(nums.begin() + toRemove[i]);
}
方法三:remove-erase
vector<int> nums = {1, 2, 3, 4, 5, 6};
// 一行代码删除所有偶数
nums.erase(remove_if(nums.begin(), nums.end(), [](int x) { return x % 2 == 0; }),nums.end());
总结
操作 | 容器 | 策略 |
---|---|---|
遍历中删除 | 所有容器 | + 使用it = erase(it); + erase(it ++ ) |
保存指针/引用/迭代器 | vector,deque,map/set,unordered_map/set | 注意扩容 |
插入 | unordered_map,vector,deque | 注意rehash,扩容 |
指针/引用/迭代器访问 | map/set | 安全 |
数据删除 | 所有容器 | 删除后不要访问原迭代器 |
insert_iterator | insert-safa容器 |
迭代器失效看起来很复杂,但只要记住几个简单的规则,就能轻松避开这个坑:
- vector: 插入或删除元素后,该位置及其后面的迭代器都会失效;如果重新分配内存,所有迭代器都会失效。
- list/forward_list: 只有被删除元素的迭代器会失效。
- map/set/multimap/multiset: 只有被删除元素的迭代器会失效。
- unordered_map/unordered_set: 插入操作可能导致所有迭代器失效(rehash);删除操作只会导致被删除元素的迭代器失效。
后记
小心resize、insert和clear