1. lambda基本语法
C++11标准为我们提供了lambda表达式的支持,而且语法非常简单明了。C++对lambda表达式的需求却非常高。最明显的就是STL,在STL中有大量需要传入谓词的算法函数,比如std::find_if、std::replace_if等。过去有两种方法实现谓词函数:编写纯函数或者仿函数。但是它们的定
义都无法直接应用到函数调用的实参中,面对复杂工程的代码,我们可能需要四处切换源文件来搜索这些函数或者仿函数。
lambda表达式的语法非常简单,具体定义如下:
[ captures ] ( params ) specifiers exception -> ret { body }
先看个例子:
void foo()
{
int x = 5;
auto foo = [x](int y)->int { return x * y; };
std::cout << foo(8) << std::endl;
}
在这个例子中,[x](int y)->int { return x * y; }是一个标准
的lambda表达式,对应到lambda表达式的语法。
[ captures ] —— 捕获列表,它可以捕获当前函数作用域
的零个或多个变量,变量之间用逗号分隔。在对应的例子中,[x]是一
个捕获列表,不过它只捕获了当前函数作用域的一个变量x,在捕获了
变量之后,我们可以在lambda表达式函数体内使用这个变量,比如
return x * y。另外,捕获列表的捕获方式有两种:按值捕获和引用
捕获,下文会详细介绍。
( params ) —— 可选参数列表,语法和普通函数的参数列
表一样,在不需要参数的时候可以忽略参数列表。对应例子中的(int
y)。
specifiers —— 可选限定符,C++11中可以用mutable,它
允许我们在lambda表达式函数体内改变按值捕获的变量,或者调用非
const的成员函数。上面的例子中没有使用说明符。
exception —— 可选异常说明符,我们可以使用noexcept来
指明lambda是否会抛出异常。对应的例子中没有使用异常说明符。
ret —— 可选返回值类型,不同于普通函数,lambda表达式
使用返回类型后置的语法来表示返回类型,如果没有返回值(void类
型),可以忽略包括->在内的整个部分。另外,我们也可以在有返回
值的情况下不指定返回类型,这时编译器会为我们推导出一个返回类
型。对应到上面的例子是->int。
{ body } —— lambda表达式的函数体,这个部分和普通函
数的函数体一样。对应例子中的{ return x * y; }。
作用域
我们必须了解捕获列表的作用域,通常我们说一个对象在某一个
作用域内,不过这种说法在捕获列表中发生了变化。捕获列表中的变
量存在于两个作用域——lambda表达式定义的函数作用域以及lambda
表达式函数体的作用域。前者是为了捕获变量,后者是为了使用变
量。另外,标准还规定能捕获的变量必须是一个自动存储类型。简单
来说就是非静态的局部变量。让我们看一看下面的例子:
int x = 0;
int main()
{
int y = 0;
static int z = 0;
auto foo = [x, y, z] {};
}
以上代码可能是无法通过编译的,其原因有两点:第一,变量x和
z不是自动存储类型的变量;第二,x不存在于lambda表达式定义的作
用域。这里可能无法编译,因为不同编译器对于这段代码的处理会有
所不同,比如GCC就不会报错,而是给出警告。那么如果想在lambda表
达式中使用全局变量或者静态局部变量该怎么办呢?马上能想到的办法是用参数列表传递全局变量或者静态局部变量,其实不必这么麻烦,直接用就行了,来看一看下面的代码:
#include <iostream>
int x = 1;
int main()
{
int y = 2;
static int z = 3;
auto foo = [y] { return x + y + z; };
std::cout << foo() << std::endl;
}
捕获值和捕获引用
捕获引用的语法与捕获值只有一个&的区别,要表达捕获引用我们
只需要在捕获变量之前加上&,类似于取变量指针。只不过这里捕获的
是引用而不是指针,在lambda表达式内可以直接使用变量名访问变量
而不需解引用,比如:
int main()
{
int x = 5, y = 8;
auto foo = [&x, &y] { return x * y; };
}
2. lambda的使用(cosnt属性)
lambda在class中使用的也比较多,lambda捕获的变量默认都是const的,我们先看如下代码。
#include <iostream>
#include <vector>
#include <array>
#include <utility>
#include <string>
#include <optional>
#include <utility>
class Widget {
public:
Widget(int value = 10) : value{value}
{
std::cout << "Widget(int) constructor:" << this << std::endl;
}
Widget(const Widget& w) : value{w.value}
{
std::cout<<"copy constructor:" << value << std::endl;
}
Widget& operator=(const Widget& rhs)
{
if (this != &rhs)
{
value = 0;//relase old resource
value = rhs.value;//assign new resource
}
std::cout << "copy assignment constructor" << std::endl;
return *this;
}
Widget(Widget&& w) : value{w.value}
{
w.value = 0;
std::cout << "move constructor:" << value << std::endl;
}
Widget& operator=(Widget&& rhs)
{
if (this != &rhs)
{
value = 0;//relase old resource
value = rhs.value;//assign new resource
rhs.value = 0;
}
std::cout << "move assignment constructor" << std::endl;
return *this;
}
~Widget()
{
if (value == 0)
{
std::cout << "0~destructor:" << this << std::endl;
}
else
{
value = 0;
std::cout << "~destructor:" << this << std::endl;
}
}
void print() const {std::cout << "value:" << value << " : " << this << std::endl;}
private:
int value{};
};
void bar(const std::optional<Widget>& w)
{
w->print();
}
class Test
{
public:
Test(){}
void foo()
{
Widget tw{100};
auto lbd = [this, tw = std::move(tw)]()/* mutable */{//使用move调用移动构造函数,不然使用拷贝构造函数
this->w.print();
tw.print();
bar(tw); // 这里tw是const的,使用move也会调用拷贝构造函数
//bar(std::move(tw)); //还是调用拷贝构造函数,wt在lambda是const的
};
lbd();
}
private:
Widget w{42};
};
int main(void)
{
Test t{};
t.foo();
}
如果把lamnda定义为mutable,则捕获列表中的不是const了,这个时候bar(std::move(tw));使用移动构造函数。
3.lambda的陷阱
reference: 现在c++语言核心特新