外观
Stage07|Modern C++ Foundation
Lesson088|Iterator、Algorithm 与 Lambda
Tags: #C++17 #Iterator #Algorithm #LambdaDifficulty: ⭐⭐⭐⭐☆
一、问题场景:循环里 erase 为什么崩溃
cpp
for (auto it = enemies.begin(); it != enemies.end(); ++it) {
if (it->dead) enemies.erase(it);
}erase 返回下一个有效迭代器,原 it 已失效;循环仍执行 ++it 就进入未定义行为。
二、本课目标
- 理解 iterator 是算法与容器之间的协议。
- 正确使用范围
[first, last)。 - 掌握常用 algorithm 和 erase-remove。
- 理解 lambda 捕获与生命周期。
- 避免在迭代中破坏容器。
三、回到 TaskSystem:用算法表达队列维护
任务系统会遇到“移除已取消任务”“统计等待任务”“按优先级排序”和“批量转换结果”。先把数据范围和容器失效规则写清,再选择 Algorithm;算法不是把循环缩短,而是把意图和边界公开给读者。
四、半开区间
标准算法通常使用:
text
[first, last)first 指向第一个元素,last 指向尾后位置。空区间自然表示为 first == last,相邻区间也易组合。
cpp
auto it = std::find(values.begin(), values.end(), target);
if (it != values.end()) { /* 找到 */ }五、用算法表达意图
cpp
std::sort(players.begin(), players.end(),
[](const Player& a, const Player& b) {
return a.score > b.score;
});
auto alive = std::count_if(players.begin(), players.end(),
[](const Player& p) { return p.hp > 0; });算法名让“排序”“计数”“查找”“变换”成为可见意图,并减少循环边界错误。
六、erase-remove idiom
C++17 删除满足条件的 vector 元素:
cpp
players.erase(
std::remove_if(players.begin(), players.end(),
[](const Player& p) { return p.hp <= 0; }),
players.end());remove_if 只把保留元素前移并返回新逻辑结尾,不改变容器 size;erase 才真正移除尾部。
七、Lambda 捕获
cpp
int threshold = 10;
auto byValue = [threshold](int x) { return x > threshold; };
auto byReference = [&threshold](int x) { return x > threshold; };引用捕获要求被捕获对象比 lambda 使用期更长。异步队列里捕获局部引用或裸 this 是高风险模式。
cpp
std::weak_ptr<Model> weak = model;
queue.post([weak] {
if (auto self = weak.lock()) self->refresh();
});八、比较器必须满足严格弱序
错误比较器:
cpp
return a.score >= b.score;当两者相等时 comp(a,b) 和 comp(b,a) 都为 true,破坏排序前提。应使用 >,相等时返回 false;需要稳定次序时增加明确的第二键或使用 stable_sort。
九、可编译验证:安全删除
cpp
#include <algorithm>
#include <iostream>
#include <vector>
int main() {
std::vector<int> values{1, 2, 3, 4, 5, 6};
values.erase(std::remove_if(values.begin(), values.end(),
[](int value) { return value % 2 == 0; }), values.end());
std::for_each(values.begin(), values.end(),
[](int value) { std::cout << value << ' '; });
}再用手写 iterator 循环实现同样功能:
cpp
for (auto it = values.begin(); it != values.end();) {
if (shouldRemove(*it)) it = values.erase(it);
else ++it;
}十、TaskSystem 中的捕获边界
同步算法中的引用捕获通常受当前作用域保护;异步任务中的引用捕获可能在函数返回后执行。[=] 还可能复制大对象或复制一个已经失效的 this 指针。写 lambda 时要先问“执行时 owner 还在吗、需要复制还是借用、容器会不会扩容”,再决定捕获方式。
十一、诊断清单
text
崩溃是否发生在 erase 后?
算法过程中是否 push_back 导致扩容?
lambda 是否引用捕获了已结束的局部对象?
比较器对相等元素是否返回 false?
算法输入区间是否来自同一容器?十二、练习与答案
1. 为什么算法的 last 通常不能解引用?
它是尾后哨兵,用来表示边界而不是元素。
2. [=] 捕获是否总安全?
不安全。它可能隐式复制大量数据,也可能复制 this 指针而非整个对象,异步执行时对象仍可能失效。
3. 何时手写循环更合适?
操作包含复杂控制流、需要同时维护多种状态,且强行组合算法反而更难读时;仍要明确失效规则。
十三、本课总结
Iterator 把遍历协议与容器解耦,Algorithm 表达操作意图,Lambda 提供局部行为。三者的安全边界集中在区间、失效和捕获生命周期。
