学习笔记2_Lambda表达式
Lambda表达式是C++11引入的一项核心特性,它允许你在需要函数对象的地方就地定义一个匿名函数,极大提升了代码的简洁性和表达能力。本质上它是编译器自动生成的匿名类(闭包类型)的实例,重载了operator()。
- 基本语法
|
[捕获列表](参数列表) mutable(可选) 异常说明(可选) -> 返回类型(可选) { 函数体 } |
|
auto f = []{ return 42; }; // 无参数,返回int auto f2 = [](int a, int b) { return a + b; }; |
- 捕获列表
捕获列表决定了 Lambda 体内可以访问外部作用域的哪些变量,以及如何访问
- 值捕获(by value)
|
int x = 10; auto f = [x] { return x * 2; }; // 拷贝 x,只读(默认) x = 20; // 不影响 Lambda 内部的副本 std::cout << f(); // 输出 20。 |
值捕获的变量在 Lambda 定义时就被拷贝,之后修改外部变量不影响内部副本。
- 引用捕获(by reference)
|
int x = 10; auto f = [&x] { return x * 2; }; // 引用捕获,访问外部变量 x = 20; std::cout << f(); // 输出 40 |
危险:Lambda 的生命周期不能超过被引用变量的生命周期,否则产生悬空引用。
- 隐式捕获
[=]:以值捕获所有使用的自动变量(包括 this)
[&]:以引用捕获所有使用的自动变量
|
int a = 1, b = 2; auto f1 = [=] { return a + b; }; // 值捕获 a,b auto f2 = [&] { return a + b; }; // 引用捕获 a,b |
- 混合捕获与默认捕获
|
int x = 1, y = 2, z = 3; auto f = [=, &y] { // 默认值捕获,但 y 是引用捕获 return x + y + z; }; auto g = [&, x] { // 默认引用捕获,但 x 是值捕获 return x + y + z; }; |
默认捕获必须写在前面,后面可以加个别的显式捕获,但不能重复捕获同一个变量。
- 初始化捕获(C++14,广义捕获)
允许在捕获列表中定义新的变量并初始化。
|
int x = 10; auto f = [y = x + 1] { // 将 x+1 的结果拷贝给 y return y; }; // 移动语义:捕获只能移动的对象 auto p = std::make_unique<int>(42); auto f2 = [ptr = std::move(p)] { // p 被移动进 Lambda return *ptr; }; |
- 捕获 this 与 *this
在非静态成员函数内,[this] 捕获当前对象的指针(引用捕获 this)。
[*this](C++17)按值捕获当前对象的副本。
|
struct S { int val = 10; auto get_lambda() { return [*this] { return val; }; // 拷贝整个对象,安全 } }; |
- 参数列表与返回类型
- 参数列表
与普通函数相同,支持auto(泛型Lambda)
|
auto add = [](auto a, auto b) { return a + b; }; std::cout << add(1, 2) << add(1.5, 2.5); |
2..返回类型推导
- 如果函数体只有一条 return 语句,自动推导返回类型。
- 否则推导为 void(除非显式指定返回类型)。
|
auto f1 = [](int i) { return i * 2; }; // 返回 int auto f2 = [](int i) -> double { return i; }; // 强制返回 double auto f3 = [](int i) { if (i < 0) return -1; // 多个返回语句,类型必须一致 else return 1; }; |
- Mutable关键字
默认情况下,Lambda 的 operator() 是 const 的,值捕获的变量不可修改。加上 mutable 后允许修改副本。
|
int x = 10; auto f = [x]() mutable { x += 5; // 修改的是 Lambda 内部的 x 副本 return x; }; std::cout << f(); // 15 std::cout << x; // 10(外部 x 不变) |
- Lumbda的类型和存储
每个 Lambda 表达式都有独有的、匿名的类型(闭包类型),无法直接写出它的类型名。通常用 auto 或 std::function 存储。
- 假设Lambda中捕获this需要注意什么?
答:生命周期管理:捕获this指针的Lambda表达式可能会在当前对象已经销毁的情况下被调用。这种情况下,访问被捕获的this指针可能导致未定义行为,因为被捕获的指针已经失效。
场景:Lambda 被存储或传递到对象生命周期之外执行。
|
class Button { public: std::function<void()> onClick; void simulateClick() { if (onClick) onClick(); } }; class Dialog { Button btn; public: Dialog() { btn.onClick = [this]() { // 危险!捕获 this this->onButtonClicked(); }; } void onButtonClicked() { /* ... */ } ~Dialog() { /* ... */ } }; void test() { auto dlg = std::make_unique<Dialog>(); // 将 dlg->btn 存储到全局或长期存在的容器中... } // dlg 销毁,但 btn.onClick 仍持有指向已销毁 Dialog 的 this 指针 |
结果:稍后调用 btn.onClick() 时,通过悬垂 this 访问成员,导致崩溃或更隐蔽的错误。
解决方法(安全捕获this):
1. 确保 Lambda 生命周期 ≤ 对象生命周期
- 只在对象内部使用 Lambda,不将其传递出去。
- 例如:在成员函数内同步调用 Lambda,不存储到成员变量或全局容器。
- 使用 std::enable_shared_from_this 和 weak_ptr
- [*this] 按值捕获对象副本
- 使用 [this, some_var] 显式捕获并确保生命周期
更多推荐
所有评论(0)