不能边遍历边用erase删除,因为erase会使后续迭代器失效,导致跳过元素或崩溃;正确做法是使用std::remove_if+erase惯用法,或C++20的std::erase_if。

为什么不能边遍历边用 erase 删除?
直接写 for (auto it = v.begin(); it != v.end(); ++it) 然后在循环里调 v.erase(it),会导致迭代器失效——erase 返回的下一个有效迭代器被丢弃,++it 就踩进已释放内存,轻则跳过元素,重则崩溃。这是新手最常掉的坑。
典型错误现象:vector 中偶数索引的元素没删干净、程序在 Debug 下报 vector iterators incompatible、Release 下行为诡异。
- 每次
erase后容器重排,后续所有迭代器/引用/指针都可能失效(除非删的是最后一个) -
erase返回值是“被删元素之后那个位置”的有效迭代器,但for循环仍会执行++it,相当于跳两格 - 哪怕加
it = v.erase(it),也得小心别漏掉--it或逻辑错位,代码难维护
std::remove_if + erase 是标准解法
这不是两个独立操作,而是一个惯用法(idiom):先用 std::remove_if 把满足条件的元素“逻辑上”挪到末尾,再用 erase 一次性物理删除。它不改变未删除元素的相对顺序,且只遍历一次,时间复杂度 O(n)。
关键点:std::remove_if 不真的删,只是重排;它返回一个指向“新逻辑尾端”的迭代器,这个迭代器之后全是待删垃圾(值未定义,但可安全擦除)。
立即学习“C++免费学习笔记(深入)”;
- 必须搭配
erase使用,单独调remove_if只是把数据搅乱,size 没变 - 谓词(predicate)可以是 lambda、函数指针或函数对象;捕获变量时注意生命周期,别用悬空引用
- 对
vector<int>这类 trivial 类型,性能几乎等同手写循环;对大对象,可考虑std::move避免拷贝(见下条)
std::vector<int> v = {1, 2, 3, 4, 5, 6};
v.erase(std::remove_if(v.begin(), v.end(), [](int x) { return x % 2 == 0; }), v.end());
// 结果:v = {1, 3, 5}
删除大对象时,用 std::remove_if + std::move 避免深拷贝
如果 vector 存的是自定义类(比如含 std::string 成员),默认 remove_if 会调用拷贝赋值,开销大。改用移动语义能显著提速。
做法很简单:在 lambda 里用 std::move 返回右值引用,配合编译器自动触发移动构造/赋值(前提是类支持移动)。
- 确保类有移动构造函数和移动赋值运算符(C++11 默认生成,除非你显式禁用或声明了拷贝操作)
- lambda 参数用值传递或
const&都行,但返回时必须std::move(x),否则还是拷贝 - 实测在含 10k 个
std::string的 vector 上,移动版比拷贝版快 3–5 倍
struct Heavy {
std::string data;
Heavy(std::string s) : data(std::move(s)) {}
Heavy(Heavy&&) = default;
Heavy& operator=(Heavy&&) = default;
};
<p>std::vector<Heavy> v = {/<em> ... </em>/};
v.erase(
std::remove_if(v.begin(), v.end(), [](Heavy& h) {
return h.data.empty();
}),
v.end()
);
替代方案:std::erase_if(C++20 起)更简洁
C++20 引入了容器专属的 std::erase_if,内部封装了 remove-erase 惯用法,一行搞定,语义清晰,且对 vector、deque、list 等都可用。
它不是通用算法,而是针对每个容器特化的函数模板,所以不用手动传 begin/end,也不用担心迭代器类型匹配问题。
- 依赖 C++20 标准,老项目升级前需确认编译器支持(GCC 10+/Clang 10+/MSVC 19.28+)
- 底层仍是 remove-erase,性能无差异,但代码更安全、易读
- 若项目暂不能切 C++20,别硬套,老老实实用
remove_if + erase更稳妥
// C++20
std::vector<int> v = {1, 2, 3, 4, 5};
std::erase_if(v, [](int x) { return x < 3; }); // v = {3, 4, 5}
真正容易被忽略的是:当谓词里抛异常时,remove_if 保证强异常安全(已处理部分不变),但 erase 那步不参与异常保证——所以整个惯用法只有基本异常安全。如果业务要求强安全,得自己包 try/catch 或换用 stable_partition + 分段删除。


















