C++中的lambda表达式捕获列表怎么用?(按值捕获与按引用捕获的区别)

按值捕获[x]复制变量,修改仅作用于副本,不影响原变量;内置类型直接赋值,类对象调用拷贝构造函数;如需修改副本须加mutable关键字。

按值捕获([x])会复制变量,修改不影响原变量

按值捕获在 lambda 创建时对捕获的变量做一次拷贝,后续 lambda 内部对它的任何修改都只作用于副本。适用于不需要同步更新、或原变量生命周期可能早于 lambda 的场景。

常见错误:误以为修改 [x] 中的 x 能改变外部变量,实际完全无关。

  • [x] 捕获的是 x 当前值的副本(调用 x 的拷贝构造函数)
  • 如果 x 是内置类型(如 int),就是简单赋值;如果是类对象,会调用拷贝构造函数
  • lambda 体中对 x 赋值(如 x = 42;)不会影响外部同名变量
  • 若需在 lambda 内修改捕获的副本,必须显式加 mutable 关键字,否则编译报错:error: assignment of read-only variable 'x'
int x = 10;
auto f = [x]() mutable {
    x = 99;  // ✅ 允许:修改副本
    std::cout << x << "\n";  // 输出 99
};
f();
std::cout << x << "\n";  // 输出 10:原变量未变

按引用捕获([&x])共享变量,修改直接影响原变量

按引用捕获不复制,而是保存对原变量的引用。lambda 内部对 x 的读写等价于直接操作外部变量。适用于需要实时反映变量变化,或避免拷贝开销的场景。

致命风险:若外部变量在 lambda 调用前已销毁(如局部变量出作用域),再调用 lambda 会导致未定义行为(常见 crash 或随机值)。

立即学习“C++免费学习笔记(深入)”;

  • [&x] 捕获的是 x 的引用,不触发拷贝或移动
  • lambda 内修改 x 就是修改原始变量,无需 mutable
  • 若捕获了局部变量的引用,而 lambda 在该局部变量生命周期结束后被调用,程序崩溃概率极高
  • 调试时常见错误信息:use of deleted valuesegmentation fault 或输出垃圾值
int x = 10;
auto f = [&x]() {
    x = 88;  // ✅ 直接改原变量
    std::cout << x << "\n";  // 输出 88
};
f();
std::cout << x << "\n";  // 输出 88

混合捕获与默认捕获([=, &y][&, x])要格外小心

混合捕获允许同时使用默认按值(=)和个别按引用(&y),或默认按引用(&)加个别按值(x)。但容易混淆哪些是引用、哪些是值,尤其当变量名相似或嵌套作用域存在同名变量时。

典型陷阱:默认按值捕获后,又用 &y 显式按引用捕获一个本可按值处理的变量,结果无意中引入悬垂引用。

  • [=, &y]:除 y 外所有变量按值捕获;y 按引用——注意 y 必须在 lambda 存活期间有效
  • [&, x]:除 x 外所有变量按引用捕获;x 按值——此时若 x 是局部变量,按值捕获安全;但其他变量全靠引用,风险集中
  • 不能同时写 [=, x][&, &y],编译器会报错:error: duplicate 'x' in capture list
  • VS 和 Clang 对混合捕获的诊断较弱,运行时出问题才暴露

捕获 this 时,[this][*this] 的本质区别

在类成员函数中,[this] 捕获的是当前对象指针的副本,lambda 内可通过 this-> 访问成员(包括非 const 成员函数);而 [*this](C++17 起支持)捕获的是当前对象的完整副本(按值),lambda 内访问的是副本的成员,与原对象彻底隔离。

这个区别常被忽略,尤其在异步回调中:用 [this] 可能导致访问已析构对象;用 [*this] 则无此风险,但无法修改原对象状态,且拷贝开销大。

  • [this]:捕获指针,轻量,但要求对象生命周期 ≥ lambda 生命周期
  • [*this]:捕获整个对象(调用拷贝构造函数),lambda 内所有成员访问都作用于副本
  • 若类含指针成员或资源句柄,[*this] 还需确保类正确实现深拷贝,否则仍可能出问题
  • 现代惯用法倾向:优先用 [weak_ptr w = shared_from_this()] 配合 lock() 安全访问,而非裸 [this]
struct Widget {
    int data = 42;
    void go() {
        auto bad = [this]() { data = 999; };           // 修改原对象
        auto good = [*this]() mutable { data = 777; }; // 修改副本,原 data 不变
    }
};
捕获列表不是语法糖,它直接决定内存布局、生命周期依赖和线程安全性。最易被忽略的是:按引用捕获的变量是否真的“活得到时候”,以及 mutable 仅解除副本的只读性,对引用捕获无效。