核心要点速览
- 语法:
[捕获列表](参数) mutable -> 返回类型 { 函数体 },必填:捕获列表 + 函数体 - 捕获方式:
[=](值)、[&](引用)、[=, &x](混合)、[this](类内)、[x=std::move(y)](C++14 移动捕获) - 陷阱:悬垂引用 /
this、mutable误用、多 return 未显式指定返回类型、STL 排序谓词非严格弱序 - 特性:无捕获转函数指针、有捕获需
std::function包装、C++14 泛型、C++17constexpr/[*this] - 用途:简化 STL 算法参数(
sort/find_if),替代短小的临时函数和仿函数
一、捕获列表
1. 捕获方式
| 捕获方式 | 说明 |
|---|---|
[=] |
捕获时机为 Lambda 创建时,仅拷贝实际使用的外部变量,外部变量后续修改不会影响副本 |
[&] |
默认引用捕获所有用到的外部变量,优点是无拷贝开销,但严禁返回带此捕获的 Lambda(极易产生悬垂引用) |
[=, &x] |
默认值捕获,仅 x 显式按引用捕获,显式捕获必须与默认方式相反,这是语法硬性规则 |
[this] |
类成员函数中默认捕获,可访问类的成员变量和成员函数,Lambda 生命周期绝对不能超过当前对象 |
[x=std::move(y)] |
C++14 新增的移动捕获,专门解决std::unique_ptr等不可拷贝对象的捕获问题,转移 y 的所有权到 x |
2. 陷阱
1:悬垂引用(返回局部变量引用捕获)
- 错误:
auto bad() { int x; return [&x]() { return x; }; }(函数返回后 x 销毁,Lambda 引用悬垂,调用时是未定义行为) - 正确:值捕获
return [x]() { return x; }(拷贝 x 形成副本,副本生命周期与 Lambda 一致,无风险)
2:悬垂this(类内返回[this] Lambda)
- 错误:
class A { int val; auto get() { return [this]() { cout << val; }; } }(若调用对象是临时对象,对象销毁后this悬垂) - 正确(C++17+):
return [*this]() { ... }(值捕获整个对象副本,彻底规避悬垂风险)
3. 捕获禁忌
- 非法:
[=, x](重复值捕获)、[&, &y](重复引用捕获)、直接捕获函数参数(需通过 Lambda 参数列表传递) - 合法:全局变量、静态变量无需捕获,可在 Lambda 内直接访问(存储在静态区,不属于局部作用域)
二、类型与存储
-
无捕获 Lambda:可隐式转换为函数指针,这是因为无状态的 Lambda 本质和普通函数一致
示例:
void(*func)() = []() {}; -
有捕获 Lambda:因持有外部变量状态,无法转换为函数指针,必须用
std::function包装,适用于函数参数、返回值等场景示例:
std::function<int(int)> f = [x=2](int a) { return a*x; }; -
大小规律:空捕获 Lambda 大小为 1(符合 C++ 空类的默认大小规则),值捕获 Lambda 大小等于捕获变量总大小,引用捕获 Lambda 大小等于指针大小(64 位系统下为 8 字节)
三、版本特性
| 版本 | 特性 | 示例 |
|---|---|---|
| C++14 | 泛型 Lambda + 移动捕获 | [](auto x){}; / [p=std::move(u)](){} |
| C++17 | constexpr Lambda |
constexpr auto sq = [](int x){ return x*x; };(编译期执行,无运行时开销) |
| C++17 | [*this]值捕获对象 |
return [*this]() { cout << val; };(解决悬垂 this 问题) |
四、STL 算法结合
1. std::sort
-
易错点:排序谓词必须满足 "严格弱序",必须用
a<b或a>b,禁用a<=b或a>=b,否则可能导致算法崩溃示例:
sort(vec.begin(), vec.end(), [desc](int a, int b) { return desc ? a>b : a<b; })
2. std::find_if
-
核心:谓词返回
bool类型,可通过捕获外部变量灵活控制筛选条件,是 STL 查找场景的常用写法示例:
find_if(strs.begin(), strs.end(), [minLen](const auto& s) { return s.size()>=minLen; })
五、易错
1. mutable
-
作用:仅解除值捕获 变量的
const限制(不影响外部变量,只是允许修改 Lambda 内部的副本) -
易错:无参数时必须带
(),这是编译器的硬性要求,缺()会直接编译失败正确:
[a]() mutable { a++; },错误:[a] mutable { a++; }
2. 返回类型推导陷阱
-
单
return语句时,编译器可自动推导返回类型;多个return语句类型不同时,必须显式指定返回类型,否则编译器无法判定统一类型正确:
[](bool f) -> double { return f ? 1 : 2.0; },错误:省略-> double(int 与 double 类型冲突)
3. C++14 泛型 Lambda
-
参数支持
auto,无需手动定义模板,就能实现通用逻辑,大幅简化代码示例:
[](auto x, auto y) { return x + y; }(支持 int、double 等任意可相加的类型)
六、问答
1. 有捕获的 Lambda 为什么不能转函数指针?
- 函数指针仅存储函数代码地址,是无状态的;而有捕获的 Lambda 持有外部变量状态,这些状态需要额外的存储空间,函数指针无法承载,因此不能转换。
2. [=]和[&]的风险?
[=]:对大对象捕获时会产生拷贝开销,影响性能;[&]:最大风险是悬垂引用,尤其是返回带此捕获的 Lambda 时,几乎必然触发未定义行为。