Lambda函数-用法/实现/广义捕获

Lambda函数-用法/实现/广义捕获

Lambda函数用法

1
2
3
4
[捕获列表] (参数) mutable noexcept/throw() -> 返回值类型
{
函数体;
};

1.[捕获列表]
[ ] 方括号用于向编译器表明当前是一个 lambda 表达式,其不能被省略。在方括号内部,可以注明当前 lambda 函数的函数体中可以使用哪些“外部变量”。

所谓外部变量,指的是和当前 lambda 表达式位于同一作用域内的所有局部变量。

捕获列表 功能
[&] 只有一个 & 符号,表示以引用传递的方式导入所有外部变量;
[&val1,&val2,…] 表示以引用传递的方式导入 val1、val2等指定的外部变量,多个变量之间没有前后次序;
[=,&val1,…] 表示除 val1 以引用传递的方式导入外,其它外部变量都以值传递的方式导入。
[=] 只有一个 = 等号,表示以值传递的方式导入所有外部变量;
[] 空方括号表示当前 lambda 匿名函数中不导入任何外部变量。
[this] 表示以值传递的方式导入当前的 this 指针。
[val1,val2,…] 表示以值传递的方式导入 val1、val2 等指定的外部变量,同时多个变量之间没有先后次序;
[val,&val2,…] 以上 2 种方式还可以混合使用,变量之间没有前后次序。

case1:不导入任何变量,[ ]为空即可,因此无法访问/修改任何外部变量

1
2
3
4
5
6
7
8
9
int a = 0, b = 0;
function<int(int,int)>fun = [=](int x,int y) -> int
{
cout<<a<<" "<<b<<endl;// error: 'a' is not captured|, error: 'b' is not captured|
return x+y;
};

fun(a,b);
cout<<a<<" "<<b<<endl;

case2: 引用 导入外部a,b变量

1
2
3
4
5
6
7
8
9
10
int a = 0, b = 0;
function<int(int,int)>fun = [=](int x,int y) -> int
{
a++;
b++;
return x+y;
};

fun(a,b);
cout<<a<<" "<<b<<endl;// 输出: 1 2

case3: 值捕获外部a,b变量时,a,b变量变成只读,不能被修改因此下面程序出错:

1
2
3
4
5
6
7
8
9
10
11
12
int a = 0, b = 0;
function<int(int,int)>fun = [=](int x,int y) -> int
{
a++;
b++;
return x+y;
};

fun(a,b);
cout<<a<<" "<<b<<endl;
//error: 'a' was not declared in this scope|
//error: 'b' was not declared in this scope|

case4:值捕获外部a,b变量,a,b变成只读,但加上mutable即可以修改

1
2
3
4
5
6
7
8
9
10
int a = 0, b = 0;
function<int(int,int)>fun = [=](int x,int y) mutable -> int
{
a++;
b++;
return x+y;
};

fun(a,b);
cout<<a<<" "<<b<<endl;

2.(参数)
和普通函数的定义一样,lambda 匿名函数也可以接收外部传递的多个参数。和普通函数不同的是,如果不需要传递参数,可以连同 () 小括号一起省略;(使用noexcept/throw 和 mutable时除外),为了减少这种不必要的记忆和困扰,建议一直加上

3.mutable
此关键字可以省略,如果使用则之前的 () 小括号将不能省略(参数个数可以为 0)。默认情况下,对于以值传递方式引入的外部变量,不允许在 lambda 表达式内部修改它们的值(可以理解为这部分变量都是 const 常量)。而如果想修改它们,就必须使用 mutable 关键字。

注意,对于以值传递方式引入的外部变量,lambda 表达式修改的是拷贝的那一份,并不会修改真正的外部变量;

4.noexcept/throw()
可以省略,如果使用,在之前的 () 小括号将不能省略(参数个数可以为 0)。默认情况下,lambda 函数的函数体中可以抛出任何类型的异常。而标注 noexcept 关键字,则表示函数体内不会抛出任何异常;使用 throw() 可以指定 lambda 函数内部可以抛出的异常类型。

​ 值得一提的是,如果 lambda 函数标有 noexcept 而函数体内抛出了异常,又或者使用 throw() 限定了异常类型而函数体内抛出了非指定类型的异常,这些异常无法使用 try-catch 捕获,会导致程序执行失败。

5.-> 返回值类型
指明 lambda 匿名函数的返回值类型。值得一提的是,如果 lambda 函数体内只有一个 return 语句,或者该函数返回 void,则编译器可以自行推断出返回值类型,此情况下可以直接省略-> 返回值类型

6. 函数体
和普通函数一样,lambda 匿名函数包含的内部代码都放置在函数体中。该函数体内除了可以使用指定传递进来的参数之外,还可以使用指定的外部变量以及全局范围内的所有全局变量。

​ 需要注意的是,外部变量会受到以值传递还是以引用传递方式引入的影响,而全局变量则不会。换句话说,在 lambda 表达式内可以使用任意一个全局变量,必要时还可以直接修改它们的值。

Lambda内部实现

Lambda实际上就是一个仿函数:即是函数是一个类,类中重载了operator (),捕获列表中的变量会作为类的private变量

举一个值捕获的例子,并添加有mutable (值捕获不能修改,修改必须加上mutable,修改的也是copy的那一份,如果想真正修改请使用引用捕获)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>
#include<functional>
using namespace std;
int main()
{
int a = 0, b = 0;
function<int(int,int)>fun = [=](int x,int y) mutable -> int
{
a++;
b++;
return x+y;
};

fun(a,b);
cout<<a<<" "<<b<<endl;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <iostream>
#include<functional>
using namespace std;
int main()
{
int a = 0;
int b = 0;

class __lambda_7_31 //lambda类的定西
{
public:
inline /*constexpr */ int operator()(int x, int y)
{
a++;
b++;
return x + y;
}

private:
int a;
int b;
public:
// inline /*constexpr */ __lambda_7_31(const __lambda_7_31 &) noexcept = default;
// inline /*constexpr */ __lambda_7_31(__lambda_7_31 &&) noexcept = default;
__lambda_7_31(int & _a, int & _b)
: a{_a}
, b{_b}
{}

};

function<int (int, int)> fun = std::function<int (int, int)>(__lambda_7_31{a, b});//初始化__lambda_7_31,并传进去a,b的值
fun.operator()(a, b);//调用lambda类中operator()(int x, int y)函数
std::operator<<(std::cout.operator<<(a), " ").operator<<(b).operator<<(std::endl);
return 0;
}

如果我们采用引用方式捕获:那么会发现a,b都变成了引用,其他无区别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class __lambda_7_31
{
public:
inline /*constexpr */ int operator()(int x, int y) const
{
a++;
b++;
return x + y;
}

private:
int & a;//引用
int & b;//引用
public:
// inline /*constexpr */ __lambda_7_31(const __lambda_7_31 &) noexcept = default;
// inline /*constexpr */ __lambda_7_31(__lambda_7_31 &&) noexcept = default;
__lambda_7_31(int & _a, int & _b)//引用
: a{_a}
, b{_b}
{}
};

初始化捕获/广义捕获(generalized lambda capture)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>
#include<functional>
using namespace std;
int main()
{
int a = 0, b = 0;
function<int(int,int)>fun = [&a,&b](int x,int y) mutable -> int
{
a++;
b++;
return x+y;
};

fun(a,b);
cout<<a<<" "<<b<<endl;
}

其中这段function<int(int,int)>fun = [&a,&b](int x,int y) mutable -> int中的[&a,&b]可以替换为:[&a = a,&b = b] ,这样看似是多此一举的,原因是这个场景中广义捕获的优势没有显现出来,考虑下面一个场景:捕获的时候直接进行move操作,得到move后的右值引用x

1
2
3
4
5
6
unique_ptr<int>b = std::make_unique<int>(5);
auto fun = [x = std::move(b)](int t = 5)
{
cout<<*x<<endl;
};
fun();

看一下它内部的细节, 初始化列表的操作时是这样操作的x{std::move(_x)}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <iostream>
#include <memory>
#include<functional>
using namespace std;
int main()
{
unique_ptr<int> b = std::make_unique<int>(5);

class __lambda_8_12
{
public:
inline /*constexpr */ void operator()(int t) const
{
std::cout.operator<<(x.operator*()).operator<<(std::endl);
}

private:
std::unique_ptr<int, std::default_delete<int> > x;
public:
// inline __lambda_8_12(const __lambda_8_12 &) = delete;
// inline __lambda_8_12 & operator=(const __lambda_8_12 &) = delete;
__lambda_8_12(std::unique_ptr<int, std::default_delete<int> > && _x)
: x{std::move(_x)}
{}

};

__lambda_8_12 fun = __lambda_8_12{std::unique_ptr<int, std::default_delete<int> >(std::move(b))};
fun.operator()(5);
return 0;
}

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!