Bootstrap

C++之lambda表达式详解

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++语言核心特新

;