C++ 学习笔记(十):Lambda 与 std::function
C 里把函数当参数传递要用函数指针,写起来繁琐,而且无法捕获上下文。Lambda 是 C++11 引入的匿名函数,可以在需要的地方直接定义,还能捕获周围的变量。
一、Lambda 基本语法
1 | [捕获列表](参数列表) -> 返回类型 { 函数体 } |
返回类型可以省略,编译器自动推导:
1 | auto add = [](int a, int b) { return a + b; }; |
没有参数时括号可以省略:
1 | auto greet = [] { printf("hello\n"); }; |
二、捕获列表
Lambda 可以捕获外部作用域的变量,这是函数指针做不到的:
1 | int threshold = 30; |
捕获方式:
1 | int x = 10, y = 20; |
按值捕获:Lambda 内有一份独立的拷贝,外部变量改变不影响 Lambda 内部:
1 | int count = 0; |
按引用捕获:Lambda 内直接操作外部变量,注意生命周期——Lambda 的生命周期不能超过被引用变量:
1 | int count = 0; |
三、Lambda 的类型和 auto
每个 Lambda 的类型都是唯一的匿名类型,只能用 auto 接收:
1 | auto f = [](int x) { return x * 2; }; |
如果需要存储或传递 Lambda,用 std::function(后面讲)或模板。
Lambda 作为参数
STL 算法大量使用 Lambda:
1 |
|
四、std::function
std::function 是一个通用的可调用对象包装器,可以存储函数指针、Lambda、仿函数:
1 |
|
存储不同类型的可调用对象
1 | int add(int a, int b) { return a + b; } |
用于回调
1 | class Button { |
五、std::function 的开销
std::function 使用了类型擦除(type erasure),内部通过虚函数调用实现,有以下开销:
- 堆分配:捕获了变量的 Lambda 如果超出内部缓冲大小,会触发堆分配
- 间接调用:每次调用有一次虚函数跳转,无法内联
- 空检查:调用前需要检查是否为空
对比裸函数指针和模板,std::function 灵活但有代价。性能敏感的路径,优先用函数指针或模板。
1 | // 函数指针:零开销,但不能捕获上下文 |
六、std::bind
std::bind 可以绑定函数的部分参数,生成一个新的可调用对象:
1 |
|
现代 C++ 里 Lambda 基本可以替代 std::bind,而且更直观:
1 | auto add5 = [](int b) { return add(5, b); }; |
std::bind 语法繁琐,类型推导复杂,错误信息难看,除非维护老代码,否则直接用 Lambda。
七、嵌入式里的使用场景
回调和事件驱动
嵌入式里按键、定时器、通信协议解析都需要回调。用 Lambda 可以直接在注册处写逻辑,不需要把回调写成单独的全局函数:
1 | timer.set_callback([&led] { |
状态机
Lambda 可以作为状态的动作函数,让状态机的代码更集中:
1 | std::array<std::function<void()>, 4> state_actions = { |
注意事项
- 裸机上避免用捕获了大量变量的 Lambda 赋给
std::function,可能触发堆分配 - 无捕获的 Lambda 可以直接转为函数指针,没有任何开销,适合 C 风格的回调接口(如 FreeRTOS 任务函数)
- 捕获
this时注意对象生命周期,Lambda 的生命周期不能超过对象
总结
- Lambda:匿名函数,可以捕获上下文,适合作为 STL 算法的谓词和回调
- 捕获方式:
[=]按值,[&]按引用,混合捕获可以精确控制 std::function:通用可调用对象包装器,灵活但有类型擦除开销- 无捕获 Lambda 可以转为裸函数指针,零开销
std::bind基本被 Lambda 取代,现代 C++ 里少用
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 Wayson's Blog!
评论




