lambda再探
有一部分在第四章的讲解里面,我再单独为lambda开一章,因为发现这里也有很多可以讲的
✅ Lambda 捕获的本质
lambda 捕获是把外部作用域的变量复制进 lambda 的闭包对象(closure object)里。
你可以把 lambda 看成一个“生成临时类的语法糖”:
1 | auto f = [x]() { return x + 1; }; |
其实等价于生成一个类:
1 | struct __Lambda { |
✅ 一、按值捕获 [x]
含义:
把外部变量 拷贝 一份到 lambda 内部。
例子:
1 | int a = 10; |
📌 特点:
- captured 值不会随外面变化
- 内部只看到“值的快照”
- 拷贝副本,因此线程安全、无悬挂引用
✅ 二、引用捕获 [&x]
含义:
lambda 内部保存的是 外部变量的引用,不拷贝。
例子:
1 | int a = 10; |
📌 特点:
- 外部的变化会反映进 lambda
- 危险:如果外部变量生命周期结束,则引用悬空!
✅ 三、隐式捕获(最常用)
[=] 按值捕获所有用到的外部变量
1 | int x = 1, y = 2; |
[&] 引用捕获所有用到的外部变量
1 | int x = 1, y = 2; |
✅ 四、混合捕获
可以混着写:
[=, &x]
默认按值捕获,但
x用引用捕获。
1 | int x=1, y=2; |
[&, x]
默认引用捕获,但
x是按值捕获。
✅ 五、捕获 this / 捕获 *this
[this]
捕获当前对象的指针(隐式捕获所有成员)。
1 | class A { |
[=*this](C++17)
深拷贝整个对象,捕获 this 指向的内容,而不是指针。
✅ 六、初始化捕获(C++14 起)
最强大、也是你当前线程池代码用到的:
[name = expr]
把任意表达式的结果绑定到捕获变量。
例如把参数 move 进 lambda:
1 | auto f = [p = std::move(vec)]() { |
这个语法就是:
- 创建一个成员变量
p - 初始化为
std::move(vec) - lambda 里使用的是内部的
p,不是外面的vec
你的代码中就是用它来“提前绑定 f 和 args”:
1 | [func = std::forward<F>(f), ... captured = std::forward<Args>(args)]() |
✅ 七、结构化绑定捕获(C++20)
1 | auto [a, b] = pair; |
✅ 八、完整捕获表总结
| 捕获方式 | 含义 | 是否拷贝 | 安全性 |
|---|---|---|---|
[x] |
按值 | ✅ | ✅ 安全 |
[&x] |
引用 | ❌ | ⚠️ 外部变量不能提前析构 |
[=] |
按值捕获所有用到的变量 | ✅ | ✅ |
[&] |
引用捕获所有用到的变量 | ❌ | ⚠️ |
[=, &x] |
默认值捕获,x引用捕获 | 混合 | 依情况 |
[&, x] |
默认引用,x 按值 | 混合 | 依情况 |
[this] |
捕获 this 指针 | ❌ | ⚠️ 成员变量可能悬空 |
[=*this] |
深拷贝当前对象 | ✅ | ✅ |
[var = expr] |
初始化捕获 | ✅ | ✅ 最强大 |
[a, b, c] |
精确指定按值捕获 | ✅ | ✅ |
✅ 九、结合你线程池代码解释(非常重要)
你问的这一行:
1 | tasks.emplace([task](){ (*task)(); }); |
这里的 [task] 是按值捕获
而 task 是一个 std::shared_ptr<packaged_task<...>>
✅ 按值捕获 shared_ptr = 拷贝 shared_ptr(引用计数 +1)
✅ lambda 内部再调用 (*task)();
✅ 这个 lambda 变成 std::function<void()> 放入队列
✅ worker 执行队列时执行 task->operator()()
✅ 任务结束后未来值写进 future
这正是正确的做法,因为:
- 保证 packaged_task 的生命周期
- 避免绑定参数被拷贝多次
- 避免悬空指针
- lambda 必须是无参(绑定参数已提前捕获)
✅ 十、一句话总结
按值捕获 = 拷贝变量到 lambda 内
引用捕获 = 捕获外部变量的引用
初始化捕获([x=expr])= 构造一个“新局部变量 x”存入 lambda,为对象绑定参数最常用
lambda 存入 std::function 时会复制整个闭包对象,包括捕获的内容
注意捕获列表里面的,只能是已有的参数,不能是新的参数,初始化捕获除外(但其等号右边也必须是上下文已经有了的参数)
初始化捕获用auto机制,解决了move的问题,减少了拷贝
前面我们提到bind和lambda都能实现参数的绑定,但实际上lambda在某种意义上代替了bind的生态位。
也就是说:
✅ **C++11 之前我们用
std::bind**(因为那时候还没有 lambda)
✅ C++11 之后 lambda 一出来,std::bind基本就被淘汰了
🧩 一、std::bind 的历史背景
在 C++98/03 时代,没有 lambda。
如果你想“预先绑定一些参数”,只能用 std::bind1st、std::bind2nd、std::bind、std::mem_fn 等函数适配器。
1 |
|
这相当于创建了一个新的函数对象:
add5(x)=add(5, x)
🧠 二、lambda 出现后做了什么
C++11 引入 lambda 表达式,可以直接写出:
1 | auto add5 = [=](int x){ return add(5, x); }; |
完全等价于上面的 std::bind(add, 5, _1),而且:
- 更直观
- 能看出参数的名字和顺序
- 支持完美转发
- 调试友好(不会生成神秘模板类型)
所以现代写法几乎都变成了:
| 原来的 bind 写法 | 现代 lambda 写法 |
|---|---|
std::bind(add, 5, _1) |
[=](auto x){ return add(5, x); } |
std::bind(&X::f, &obj, _1) |
[&](auto x){ obj.f(x); } |
std::bind(&X::set, &obj, 42) |
[&]{ obj.set(42); } |
🧩 三、为什么 lambda 能替代 bind?
| 对比点 | std::bind |
lambda |
|---|---|---|
| 引入版本 | C++98 | C++11 |
| 可读性 | 差(_1, _2 模糊) |
高(参数清晰) |
| 类型安全 | 不安全(模板推断复杂) | 完美类型推断 |
| 性能 | 可能多层包装 | 编译期内联优化 |
| 调试体验 | 难读难看 | 直接看 lambda 逻辑 |
| 完美转发支持 | ❌ 不支持 | ✅ 支持(std::forward) |
| 捕获外部变量 | ❌ 不能 | ✅ 能捕获(按值、引用、自定义) |
🔹 最大区别:std::bind 只能绑定参数,不能捕获外部变量。
而 lambda 可以随意捕获任意作用域内的对象。
🧩 四、一个实际对比示例
🏷 用 bind 的旧写法(C++03 / 早期 C++11)
1 |
|
🏷 现代 lambda 写法
1 | Worker w; |
看出来了吗?
std::bind语法啰嗦;- 不支持捕获;
- 编译错误信息又长又难懂;
- 读起来不如 lambda 直觉。
🧩 五、lambda = 语法层面的 bind 超集
可以认为:
lambda 是“语法层面内置”的 bind + function object + capture 支持。
本质等价于:
1 | auto lam = [captured = std::move(obj)](int x, int y) { return captured.func(x, y); }; |
vs
1 | auto lam = std::bind(&Class::func, obj, std::placeholders::_1, std::placeholders::_2); |
但 lambda:
- 能 inline 编译
- 能直接在模板中推断类型
- 不需要占位符
_1_2 - 可以完美转发参数
🧩 六、在你的线程池上下文里
你那段代码:
1 | auto task = std::make_shared<std::packaged_task<return_type()>>( |
如果没有 lambda,你只能这么写(旧时代的方式):
1 | auto bound = std::bind(std::forward<F>(f), std::forward<Args>(args)...); |
但是:
std::bind会复制所有参数;- 无法
move; - 无法控制捕获方式;
- 不支持完美转发;
- 性能可能更差。
所以现代线程池几乎都写成 lambda。
🧩 七、结论总结
| 项目 | std::bind |
lambda |
|---|---|---|
| 定义时间 | C++98 | C++11 |
| 功能 | 绑定参数创建函数对象 | 绑定外部变量创建函数对象 |
| 是否立即调用 | ❌ 延迟 | ❌ 延迟 |
| 语法可读性 | 差 | 高 |
| 性能 | 一般 | 高(可内联) |
| 类型推断 | 弱 | 强(自动) |
| 捕获变量 | ❌ 不支持 | ✅ 支持任意方式 |
| 是否推荐使用 | ❌ 不推荐(现代C++弃用) | ✅ 推荐 |
✅ 一句话总结:
lambda 是现代 C++ 的“语法级 bind”,功能更强、语法更直觉、性能更好。
可以这么说:
std::bind是旧时代没有 lambda 时的权宜之计,
lambda 是 C++ 正式把“函数式编程”纳入语言级语法的结果。


