Lambda表达式使用方法
lambda表达式主要包括如图所示的六个部分,各部分作用如下:
lambda clause,捕获子句,用来表示对表达式外变量的获取形式,[=]表示按值捕获,[&]表示按引用捕获,[]表示啥都没用
parameter list,参数列表,optional,用来声明表达式中使用的参数,
mutable specification,可变声明,optional,对于按值捕获的变量理论上不可修改,如果想要修改需要添加此声明,例如:
1
2
3
4
5
6
7
8
9
10
11
12// captures_lambda_expression.cpp
// compile with: /W4 /EHsc
using namespace std;
int main()
{
int m = 0;
int n = 0;
[&, n] (int a) mutable { m = ++n + a; }(4);//修改了n的值,但是由于是按值捕获,所以变化不会反映到外部的变量n上
cout << m << endl << n << endl; //m = 5 n = 0
}exception specification,异常声明,optional,用来声明对于异常的处理,此处可以指定
noexcept表示该表达式不抛出异常,注意,如果在lambda体中仍然使用了throw,编译器会警告trailing-return-value, 返回类型,optional,在只有一个返回值时,lambda表达式会自动推断返回值类型,故无需指定,同样也无需使用auto关键字;然而,当返回语句包含多个时,lambda表达式会将它们的类型推断为void,如下:
1
2
3auto x1 = [](int i){ return i; }; // OK: return type is int
auto x2 = []{ return{ 1, 2 }; }; // ERROR: return type is void, deducing
// return type from braced-init-list isn't validlambda body,lambda体,此处是表达式的核心部分,用来指定对数据的操作
Lambda表达式,函数对象和函数指针的区别
在C++标准库中,函数指针和函数对象的使用非常频繁,但是它们都有各自的局限性
- 函数指针:语法开销小,但是无法保存上一次执行的状态
- 函数对象:可以维持状态但是开销大
因此,lambda表达式将两者优点结合并尽可能避免它们的缺点。它可以像函数对象一样,应用灵活且可以保存之前的运行状态,同时它的语法特点使得lambda表达式并不需要显式的类定义,下方是将lambda表达式与STL库结合的一段代码,体现了它相较于函数对象的优势:
1 | // even_lambda.cpp |
但是,如果说之后这个项目还需要进一步改进,建议还是使用函数对象以方便后续维护,lambda表达式,尤其涉及到多层嵌套和高阶lambda函数时,可读性会非常差。
Lambda表达式中需要注意的几个问题
值捕获与引用捕获(capture by value vs. capture by reference)
要点:同一个变量只能以一种形式捕获。对于指针变量,需要区分其指向值的更改(值捕获和引用捕获均会同步改变)和指向地址的更改(只有引用捕获会同步改变)。
1 | // function_lambda_expression.cpp |
this指针
VS2017后可以对this指针使用值引用,即在捕获子句声明[*this]使用类中的其它变量,在lambda表达式异步或者并行执行时,这样的效率更高(未验证),同时也支持[this]或者[]隐式调用this
1 | // capture "this" by reference |
lambda表达式嵌套和高阶lambda函数
lambda表达式支持在一个lambda表达式中定义另一个lambda表达式并传递参数,例如:
1 | // nesting_lambda_expressions.cpp |
同样,lambda表达式也支持使用另一个lambda表达式作为参数:
1 | // higher_order_lambda_expression.cpp |