C++ lambda 捕获完整梳理

C++ lambda 捕获完整梳理

目录

[C++ lambda 捕获完整梳理](#C++ lambda 捕获完整梳理)

[1. C++11 四种基础捕获](#1. C++11 四种基础捕获)

[1. 值捕获 x](#1. 值捕获 [x])

[2. 引用捕获 \&x](#2. 引用捕获 [&x])

[3. 默认全部值捕获 =](#3. 默认全部值捕获 [=])

[4. 默认全部引用捕获 \&](#4. 默认全部引用捕获 [&])

混合捕获(部分引用、部分值)

[2. C++14 新增:初始化捕获 init‑capture(重点,前面聊过move捕获)](#2. C++14 新增:初始化捕获 init‑capture(重点,前面聊过move捕获))

[3. this捕获,捕获类成员](#3. this捕获,捕获类成员)

[4. mutable 干什么](#4. mutable 干什么)

[5. 常见坑总结](#5. 常见坑总结)

速查表

简单记忆

[6. 总结](#6. 总结)

[lambda ≈ C++ 的闭包,但不是完全等价](#lambda ≈ C++ 的闭包,但不是完全等价)

两种捕获本质

补充几个关键点

和其他语言闭包简单对比

简短总结


lambda语法格式:

复制代码
 [捕获子句](参数) mutable ->返回类型 {函数体};

捕获写在[]里面,作用:把外部作用域的变量,带入lambda内部使用

注意:C++11、C++14有差异,C++14新增初始化捕获(init‑capture)。

1. C++11 四种基础捕获

1. 值捕获 [x]

把外部变量拷贝一份进lambda内部,lambda内部是副本,外部原变量不受影响。

复制代码
 int a = 10;
 auto f = [a](){
     // a 是拷贝出来的副本
     return a;
 };
 a = 20;
 f(); // 返回10,不受外部a修改影响

默认lambda的operator()const值捕获的副本默认不能修改 ,要改需要加mutable

复制代码
 auto f = [a]() mutable {
     a = 100; // 修改的是lambda内部副本,外部a不变
     return a;
 };

2. 引用捕获 [&x]

捕获外部变量的引用,lambda直接操作外部原本变量,没有拷贝

复制代码
 int a =10;
 auto f = [&a](){
     a = 99;
 };
 f();
 // 外部a已经变成99

⚠️巨大坑:引用捕获保存的是引用,如果lambda生命周期长于外部变量,会产生悬空引用,UB崩溃。

3. 默认全部值捕获 [=]

[=]:lambda用到什么外部变量,就自动做值拷贝。

复制代码
 int a=1,b=2;
 auto f = [=](){ return a+b; };

4. 默认全部引用捕获 [&]

[&]:用到的外部变量全部引用捕获。

复制代码
 int a=1,b=2;
 auto f = [&](){ a = 100; };

混合捕获(部分引用、部分值)

复制代码
 int a=10,b=20;
 auto f = [=, &a](){
     // a引用;b值拷贝
 };
 auto f2 = [&, b](){
     // &代表其余引用;b值拷贝
 };

规则:[=, &x] 大部分值,个别引用;[&, x]大部分引用,个别值。 不能同时写[=,&],两个默认不能一起开。

2. C++14 新增:初始化捕获 init‑capture(重点,前面聊过move捕获)

语法:[变量名 = 表达式] 在lambda内部创建成员变量,表达式可以是任意代码,可以std::move

复制代码
 std::unique_ptr<int> p = std::make_unique<int>(100);
 // val是lambda内部新成员,p移动进去
 auto f = [val = std::move(p)](){
     return *val;
 };
 // p已经被move走,外部p=nullptr
  • val = std::move(p)不是引用,不是拷贝,内部构造val,执行move

  • 可以写任意表达式,不一定move

复制代码
 auto f = [x = 1+2](){ return x; };

⚠️C++11不支持这种写法!C++11只能捕获已经存在的外部变量,不能在[]里面做赋值、move。

3. this捕获,捕获类成员

复制代码
 struct Demo {
     int v = 10;
     void test() {
         auto f = [this](){
             return this->v; // 捕获this指针,访问成员
         };
         f();
     }
 };

C++17还新增[*this],把整个对象拷贝一份进lambda,而不是捕获指针this。

复制代码
 auto f = [*this](){ return v; }; //拷贝整个对象,不怕对象析构悬空

4. mutable 干什么

lambda默认生成的operator() const,捕获到的内部成员不能修改。

  • 值捕获副本想修改;init‑capture移动进来的成员想修改,需要加mutable
复制代码
 auto f = [val = std::move(p)]() mutable {
     *val = 200;
     return *val;
 };

不加mutable编译报错。

5. 常见坑总结

  1. 引用捕获[&],lambda生命周期超过外部变量 → 悬空引用,崩溃,异步回调非常容易踩。

  2. [=]值捕获:捕获指针,只是拷贝指针,不是拷贝指针指向内存。

复制代码
 int* p = new int(10);
 auto f = [=](){ return *p; };
 delete p;
 f(); // 悬空!拷贝的只是指针p,不是堆上数据
  1. unique_ptr不能拷贝,C++11无法直接捕获;C++14用[val = std::move(p)]移动捕获。

  2. [this]捕获的是原始对象指针,如果对象销毁,lambda执行就悬空。C++17优先[*this]拷贝对象。

速查表

写法 含义 C++版本
[x] 值拷贝变量x C++11
[&x] 引用变量x C++11
[=] 用到的变量全部值拷贝 C++11
[&] 用到的变量全部引用 C++11
[val = expr] 初始化捕获,内部构造成员,支持move C++14
[this] 捕获对象指针 C++11
[*this] 拷贝整个对象 C++17

简单记忆

  • & → 引用(操作原变量,小心悬空)

  • 不带& → 值拷贝/副本

  • xxx = ... → C++14初始化捕获,在lambda内部新建成员。


6. 总结

lambda ≈ C++ 的闭包,但不是完全等价

闭包:函数 + 捕获的外部环境变量。 C++ lambda 编译器会生成一个匿名类(闭包对象),捕获的变量变成这个类的成员变量。

两种捕获本质
  1. 值捕获 [a] / C++14 初始化捕获 [x = ...] 把变量拷贝 /move 进闭包对象内部

闭包对象自己持有一份数据;就算原来栈上变量销毁,闭包不受影响,安全。

  1. 引用捕获 [&a][&] 闭包内部存的是引用 / 指针指向外部栈变量不拷贝数据

⚠️巨大风险:如果 lambda(闭包对象)活得比栈上局部变量更久,栈变量已经出作用域销毁,闭包拿着悬空引用,访问就是未定义行为,随机崩溃。

复制代码
 std::function<void()> bad_case() {
     int local = 100;
     auto f = [&local](){
         printf("%d\n", local); // 危险!引用栈上local
     };
     return f; // 返回闭包,函数退出,local栈内存销毁
 }
 ​
 // 外面调用,此时local早已销毁,悬空UB
 auto fn = bad_case();
 fn();

上面这个是 C++ 开发最经典的坑。

如果改成值捕获[local],就安全:把 local 拷贝进 lambda 对象,函数退出栈变量销毁不影响闭包副本。

补充几个关键点
  1. lambda 只是语法;闭包对象可以存到 std::function 里面,延长生命周期,这也是悬空引用问题高发场景。

  2. [=] 默认值捕获:如果捕获的是指针,只是拷贝指针值,不会拷贝指针指向堆内存,依然会悬空。

复制代码
 int* p = new int(10);
 auto f = [=](){ return *p; };
 delete p;
 f(); // 依然悬空,拷贝的只是指针,不是*p的数据
  1. [this]捕获的是对象指针,对象析构之后再调用 lambda,同样悬空。C++17 的[*this]会把整个对象拷贝进去,可以规避这个问题。
和其他语言闭包简单对比
  • JS/Python 闭包:引用外部变量,语言有 GC,变量不会被提前销毁。

  • C++ 没有 GC;栈变量自动销毁,引用捕获闭包不会延长栈变量生命周期,这是和 GC 语言最大区别。

简短总结

lambda 就是 C++ 实现闭包的语法; ✅值捕获:把数据搬进闭包对象,安全; ⚠️引用捕获只是拿引用,不会延长栈变量生命周期;一旦 lambda 晚于栈变量销毁,直接悬空 UB,异步回调、返回 lambda 的时候一定要高度警惕。

工程小习惯:异步回调、要保存起来延后执行的 lambda,尽量少用[&]默认引用捕获

相关推荐
linx2951 小时前
阶段三讲义:类型与对象(Classes & Essential Operations)
c++
東隅已逝,桑榆非晚1 小时前
c++内存管理
c++·笔记
是个西兰花1 小时前
C++11:线程库与线程安全问题
开发语言·c++
少司府2 小时前
C++进阶:智能指针
开发语言·数据结构·c++·b树·算法·c·智能指针
水饺编程3 小时前
编程数学:三角函数基础01,直角三角函数
c语言·c++·windows·visual studio
初願致夕霞3 小时前
C/C++传统程序内存分布(代码实测)
java·c语言·c++
牛油果子哥q3 小时前
C++对接LLM完整工程:异步HTTP请求、JSON解析、超时容错、重连兜底
c++·http·json
会周易的程序员3 小时前
aiDgeController软PLC控制通讯协议文档
c++·物联网·网关·iot·ipc·进程间通讯
(Charon)4 小时前
【C++】网络缓冲区设计(四):epoll + Reactor中Buffer的完整读写流程
开发语言·c++