C++中的lambda捕获列表[=]和[&]的区别?(值传递与引用传递的差异)

值捕获[=]仅拷贝lambda体实际使用的自动变量值,不捕获this、静态/全局变量;引用捕获[&]以引用方式捕获使用变量,有生命周期风险;混合捕获可覆盖默认方式,[=, &x]表示x引用捕获其余值捕获。

捕获列表 [=] 是值捕获,但不等于“所有变量都拷贝”

使用 [=] 时,lambda 会以值方式捕获当前作用域中**被 lambda 体实际使用到的**自动变量(即 odr-used)。没被用到的变量不会被捕获,哪怕写了 [=]。它不会捕获 this(除非显式写 [=, this]),也不会捕获静态局部变量或全局变量(它们本就不需要捕获)。

注意:即使变量是引用类型(如 int& x = y;),[=] 捕获的仍是该引用所指向对象的**值副本**,不是原引用本身。

  • 若捕获对象不可拷贝(如 std::unique_ptr),编译失败
  • 若对象很大,可能带来不必要的拷贝开销
  • 修改捕获后的变量,不影响原变量

捕获列表 [&] 是引用捕获,生命周期风险必须手动管理

[&] 以引用方式捕获所有被 lambda 体实际使用的自动变量。它同样不捕获未使用的变量,也不捕获 this 自动(需显式加 [&, this])。

关键问题是:如果 lambda 在定义它的作用域结束后仍被调用(比如返回后存储、异步执行),而它引用的变量已经销毁,行为就是未定义的——常见 crash 或读脏内存。

  • 捕获的是变量本身的引用,修改 lambda 内部的 x 就等于修改原变量
  • 对只读场景更轻量(无拷贝),但极易误用
  • 不能捕获临时对象(如函数返回的右值),因为其生命周期通常短于 lambda

混合捕获 [=, &x] 和 [=, x] 的行为差异

C++ 允许混合捕获,但规则严格:一旦用了 [=][&],其余显式捕获必须与之“方向一致”,除非显式覆盖。例如:

int a = 1, b = 2;
auto f1 = [=, &a]() { a++; b++; }; // 合法:a 引用捕获,b 值捕获
auto f2 = [&a, b]() { a++; b++; }; // 合法:a 引用,b 值
auto f3 = [=, b]() { };            // 错误:重复指定 b 的捕获方式

特别注意:[=, &x] 中的 &x 是对默认值捕获的**覆盖**,不是追加;而 [&, x] 中的 x 是对默认引用捕获的覆盖(此时 x 是值捕获)。

  • [=, &x]:绝大多数变量值捕获,仅 x 引用捕获
  • [&, x]:绝大多数变量引用捕获,仅 x 值捕获
  • 不能混用 [=, x]x 已被 = 覆盖,再写是重复)

捕获 this 的隐含规则和陷阱

在类成员函数中,[=][&] 都**不会自动捕获 this 指针**。这意味着 lambda 无法访问 this->member,除非你显式写 [=, this][&, this]

更隐蔽的问题是:即使你写了 [=],它也只捕获 this 指针的值(即对象地址),而不是对象本身;所以你可以安全调用成员函数(只要对象还活着),但不能假设对象数据没被析构。

  • 在异步回调中,[=, this] 很常见,但务必确保对象生命周期长于 lambda 执行期
  • shared_from_this() 配合 std::shared_ptr

    是更安全的替代方案
  • [this](无 = 或 &)等价于 [=, this],C++17 起允许
捕获的本质不是语法糖,而是决定 lambda 闭包对象里存什么、怎么存;值捕获安全但可能重,引用捕获轻量但危险——选哪个,得看变量用途、大小、生命周期,以及 lambda 是否逃逸出当前作用域。