Maxbad`Blog

c++ Lambda 表达式

2020-05-12 · 5 min read

Lambda 表达式,实际上就是提供了一个类似匿名函数的特性,而匿名函数则是在需要一个函数,但是又不想费力去命名一个函数的情况下去使用的。
基本语法如下:
[ caputrue ] ( params ) opt -> ret { body; };

  • capture 是捕获列表;
  • params 是参数表;(选填)
  • opt是函数选项 可以填mutable,exception,attribute(选填)

mutable说明lambda表达式体内的代码可以修改被捕获的变量,并且可以访问被捕获的对象的non-const方法。
exception说明lambda表达式是否抛出异常以及何种异常。
attribute用来声明属性。

  • ret 是返回值类型(拖尾返回类型)。(选填)
  • body 是函数体。

捕获列表:lambda表达式的捕获列表精细控制了lambda表达式能够访问的外部变量,以及如何访问这些变量。

  1. [] 不捕获任何变量。
  2. [&] 捕获外部作用域中所有变量,并作为引用在函数体中使用(按引用捕获)。
  3. [=] 捕获外部作用域中所有变量,并作为副本在函数体中使用(按值捕获)。注意值捕获的前提是变量可以拷贝,且被捕获的变量在 lambda 表达式被创建时拷贝,而非调用时才拷贝。如果希望lambda表达式在调用时能即时访问外部变量,我们应当使用引用方式捕获。
int a = 0;
auto f = [=] { return a; };
a+=1;
cout << f() << endl;       //输出0
int a = 0;
auto f = [&a] { return a; };
a+=1;
cout << f() <<endl;       //输出1
  1. [=,&foo] 按值捕获外部作用域中所有变量,并按引用捕获foo变量。
  2. [bar] 按值捕获bar变量,同时不捕获其他变量。
  3. [this] 捕获当前类中的this指针,让lambda表达式拥有和当前类成员函数同样的访问权限。如果已经使用了&或者=,就默认添加此选项。捕获this的目的是可以在lamda中使用当前类的成员函数和成员变量。
class A
{
 public:
     int i_ = 0;

     void func(int x,int y){
         auto x1 = [] { return i_; };                   //error,没有捕获外部变量
         auto x2 = [=] { return i_ + x + y; };          //OK
         auto x3 = [&] { return i_ + x + y; };        //OK
         auto x4 = [this] { return i_; };               //OK
         auto x5 = [this] { return i_ + x + y; };       //error,没有捕获x,y
         auto x6 = [this, x, y] { return i_ + x + y; };     //OK
         auto x7 = [this] { return i_++; };             //OK
};

int a=0 , b=1;
auto f1 = [] { return a; };                         //error,没有捕获外部变量    
auto f2 = [&] { return a++ };                      //OK
auto f3 = [=] { return a; };                        //OK
auto f4 = [=] {return a++; };                       //error,a是以复制方式捕获的,无法修改
auto f5 = [a] { return a+b; };                      //error,没有捕获变量b
auto f6 = [a, &b] { return a + (b++); };                //OK
auto f7 = [=, &b] { return a + (b++); };                //OK

注意 : 虽然按值捕获的变量值均复制一份存储在lambda表达式变量中,修改他们也并不会真正影响到外部,但我们却仍然无法修改它们。如果希望去修改按值捕获的外部变量,需要显示指明lambda表达式为mutable。被mutable修饰的lambda表达式就算没有参数也要写明参数列表。
原因:lambda表达式可以说是就地定义仿函数闭包的“语法糖”。它的捕获列表捕获住的任何外部变量,最终会变为闭包类型的成员变量。按照C++标准,lambda表达式的operator()默认是const的,一个const成员函数是无法修改成员变量的值的。而mutable的作用,就在于取消operator()const

int a = 0;
auto f1 = [=] { return a++; };                //error
auto f2 = [=] () mutable { return a++; };       //OK

lambda表达式的大致原理 :每当你定义一个lambda表达式后,编译器会自动生成一个匿名类(这个类重载了()运算符),我们称为闭包类型(closure type)。那么在运行时,这个lambda表达式就会返回一个匿名的闭包实例,是一个右值。所以,我们上面的lambda表达式的结果就是一个个闭包。对于复制传值捕捉方式,类中会相应添加对应类型的非静态数据成员。在运行时,会用复制的值初始化这些成员变量,从而生成闭包。对于引用捕获方式,无论是否标记mutable,都可以在lambda表达式中修改捕获的值。至于闭包类中是否有对应成员,C++标准中给出的答案是:不清楚的,与具体实现有关。

查看更多C++11常用新特性

更多参考