lambda 表达式
总结:
lambda
表达式原理:被编译器转换为类+初始化对象的代码;- 格式:
[captures list] (params list) specifiers exception -> retType {funtion body}
- 按值捕获和按引用捕获的优缺点以及解决方法;
一. lambda
原理
lambda函数会被编译器转换为类,并定义实现一个operator()
在https://cppinsights.io/
这个网站里我们看下,
顺便推荐下这个网站 cppinsights
能看到你写的代码经过编译器处理之后的代码样式,非常好用(^ω^)
#include <cstdio>
int main()
{
int value = 10;
auto func = [value](int a, int b) {
printf("%d\n", value);
return a + b;
};
func(5, 6);
}
经过编译器处理之后会变成如下代码:
#include <cstdio>
int main()
{
int value = 10;
class __lambda_6_17
{
public:
inline /*constexpr */ int operator()(int a, int b) const
{
printf("%d\n", value);
return a + b;
}
private:
int value;
public:
__lambda_6_17(int & _value)
: value{_value}
{}
};
__lambda_6_17 func = __lambda_6_17{value};
func.operator()(5, 6);
return 0;
}
通过上面的两段代码对比,其实可以看到lambda
函数func
会被编译器转换为class __lambda_6_17
,并且这个类重载了函数调用运算符operator()
。
其中就是c++中仿函数的概念
而重载operator()
就可以让一个类型像函数那样调用,类的行为和函数相似
像上面的类__lambda_6_17
,其实这样调用__lambda_6_17();
就会自动调用内部的operator()
二. lambda expression
格式
[captures list] (params list) specifiers exception -> retType {funtion body}
-
specifiers
:optional
可选限定符,默认是const
,可选mutable
;- 这个
const
有什么用呢? - int operator()(int a, int b) const
- 就是上面这个
const
,表示函数体内不能修改捕获的变量;如果是mutable
,那可以在函数体内修改捕获变量的值。
- 这个
-
-> retType
:optional
返回值类型,大多数情况下返回类型都可以自动推导出来;有些场景不行; -
(params list)
:optional
参数列表,可以不写参数; -
[captures list]
:- 捕获列表:有按值捕获和按引用捕获,捕获的变量都会成为类内的成员变量;不需要捕获静态变量和全局变量(没必要转换为类内的成员变量);
- 捕获时机:捕获发生在lambda定义的时候,而非调用的时候,所以在定义之后更改了捕获变量的值,其实这个lambda内部使用的还是lambda定义之前的变量的值;
- 初始化捕获;
(1) 捕获列表
- 按值捕获的问题
- 捕获的是变量副本,而非变量本身;这就导致一些不可复制的对象不能被捕获,如:
unique_ptr
; - 捕获时机在
lambda
创建时,后续变量被更新,lambda
内部的变量也不会再发生变化; - 捕获变量是比较大的对象,会有较大的资源消耗。
- 按引用捕获的问题
- 捕获时会创建一个对外部变量的引用,如果外部变量的声明周期结束了,但是
lambda
内部还在使用就会出现问题;
来个例子,如何实现?主要是能够让lambda
捕获的变量和lambda
的声明周期不一致即可:
#include <cstdio>
auto func() {
int value = 10;
auto lambda_func = [&value]() { printf("%d\n", value); };
return lambda_func;
}
int main() {
auto func_var = func(); // 这里延长了lambda的声明周期
func_var();
}
(2) 捕获时机:
下面的代码证明了捕获的时机发生在lambda
函数定义的时候,下面的value
在func
调用之前再次修改了值,但是函数输出的结果依旧是10。
注意:
- 对于按值捕获来说,定义
auto func = [value](int a, int b) {}
的这段代码被编译器转换成了两部分:class __lambda_5_15 {};
和__lambda_5_15 func = __lambda_5_15{value};
。其实在定义之后,直接就用捕获的变量的值调用了构造函数。 - 对于按引用捕获来说,类内的成员变量是一个引用变量,所以它的值是会随着捕获变量更新而改变的。
#include <cstdio>
int main() {
int value = 10;
auto func = [value](int a, int b) {
printf("%d\n", value);
return a + b;
};
value = 20;
func(5, 6); // output: 10
}
#include <cstdio>
int main()
{
int value = 10;
class __lambda_5_15
{
public:
inline /*constexpr */ int operator()(int a, int b) const
{
printf("%d\n", value);
return a + b;
}
private:
int value;
public:
__lambda_5_15(int & _value)
: value{_value}
{}
};
__lambda_5_15 func = __lambda_5_15{value}; // 这里在定义class之后,直接调用了构造函数
value = 20;
func.operator()(5, 6);
return 0;
}
(3)初始化捕获
lambda
对外部进行捕获时,实际执行的是赋值初始化的操作:
auto func1 = [value]() {}
等价于 auto func1 = [value = value]() {}
这样的话,对于那些不可复制的对象或者比较大的对象来说,就可以使用移动语义了:
auto func3 = [unique_value_in = std::move(unique_value)]() {}
具体的就是下面这段代码:
#include <cstdio>
#include <memory>
#include <utility>
int main() {
int value = 10;
std::unique_ptr<int> unique_value = std::make_unique<int>(5);
auto func1 = [value = value]() { printf("%d\n", value); };
auto func2 = [v = value]() { printf("%d\n", v); };
// auto func3 = [unique_value]() { printf("%d\n", *unique_value); };
auto func3 = [unique_value_in = std::move(unique_value)]() {
printf("%d\n", *unique_value_in);
};
func1();
func2();
func3();
}
标签:__,int,捕获,value,填坑,C++,func,lambda
From: https://www.cnblogs.com/pplearn/p/18048487