c++11新特性:lambda表达式使用方法,实现原理

更多参见:C++面试题系列:C++11新特性

提供了一个类似匿名函数的特性,匿名函数就是在需要一个函数,但是又不想费力去命名一个函数的情况下使用的。

基本语法

基本语法:

[caputrue](params)opt->ret{body;};

capture是捕获列表

params是参数表;选填

opt:函数选项;可以填mutable,exception,attribute(选填)。mutable:说明lambda表达式内的代码可以修改被捕获的变量,并且可以访问被捕获的non-const方法;exception:说明lambda表达式是否抛出异常以及何种异常;attribute:用来声明属性。

ret:返回值类型(拖尾返回类型)。(选填)

body:是函数体。

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

 []不捕获任何变量;

[&]捕获外部作用域中的所有变量,并作为引用在函数体中使用(按引用捕获);

[=]捕获外部作用域中所有变量,并作为副本在函数体中使用(按值捕获),注意捕获的前提是变量可以拷贝,且捕获的变量在lambda表达式在创建时拷贝,而非调用时拷贝。如果希望lambda表达式在调用时能即时访问外部变量,我们应当使用引用方式捕获。

按值捕获:

1 int a=0;
2 auto f=[=]{return a;};
3 a+=1;
4 cout<<f()<<endl;    //输出0

按引用捕获:

1 int a=0;
2 auto f=[&a]{return a;};
3 a+=1;
4 cout<<f()<<endl;    //输出1

 [=,&foo]按值捕获外部作用域中的所有变量,并按引用捕获foo变量。

[bar]按值捕获bar变量,同时不捕获其他变量。

[this]捕获当前类的this指针,让lambda表达式拥有和当前类成员函数同样的访问权限。如果已经使用了&或者=,就默认添加此选项。捕获this的目的是可以在lambda中使用当前类的成员函数和成员变量。

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

f4,虽然按值捕获的变量值都复制一份存储在lambda表达式变量中,修改他们并不会真正影响到外部。如果想修改按值捕获的外部变量,需要指明lambda表达式为mutable,被mutable修饰的lambda表达式就算没有参数也要声明参数列表。

原因:lambda表达式可以说事就地定义仿函数闭包的“语法糖”。它的捕获列表捕获住的任何外部变量,最终会变成闭包类型的成员变量。按照C++11标准,lambda表达式的operator()默认是const的,一个const成员函数无法修改成员变量。而mutable的作用就是修改operator的const的。

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

 实现原理:

每当定义一个lambda表达式之后,编译器会自动生成一个匿名类(这个类重载了()运算符),我们称为闭包类型(closure type)。

运行时lambda表达式就会返回一个匿名的闭包实例,是一个右值。所以上面的lambda表达式的结果就是一个个闭包。

对于复制传值捕获方式,类中会添加非静态成员变量,运行时,会用复制的值初始化这些非静态成员变量,从而生成闭包。

对于引用捕获方式,无论是否标记为mutable,都可以修改被捕获变量的值。引用捕获变量是否为类的成员变量,C++11不清楚,跟具体实现有关。

lambda表达式是不能被赋值的:

1 auto a=[]{cout<<"A"<<endl;};
2 auto b=[]{cout<<"B"<<endl;};
3 a=b;    //非法,lambda无法赋值
4 auto c=a;    //合法,生成一个副本

闭包类型禁用了赋值操作符,但没有禁用复制构造函数,所以你任然可以使用一个lambda表达式去初始化另一个lambda表达式从而产生副本。

安全性

在多种捕获方式中,最好不要使用[=]和【&】默认捕获所有变量

默认引用捕获所有变量,很有可能会出现悬挂引用,因为引用捕获不会延长引用的变量的生命周期。

1 std::function<int(int)> add_x(int x){
2     return [&](int a){return x+a;};
3 }

上面的函数返回了一个lambda表达式,x是一个临时变量,调用add_x之后会销毁。 但是lambda表达式却引用了这个x,当调用lambda表达式时,引用是一个垃圾值,会产生无意义的结果。这种情况使用默认传值方式可以米面悬挂引用。

但是采用默认值捕获所有变量任然有风险,

class Filter
{
public:
    Filter(int divisorVal):
        divisor{divisorVal}
    {}

    std::function<bool(int)> getFilter()
    {
        return [=](int value) {return value % divisor == 0; };
    }

private:
    int divisor;
};

这个类中有一个成员方法,可以返回一个lambda表达式,这个表达式使用了累的成员变量divisor。你可能以为lambda表达式捕获了divisor的一个副本,但实际上并没有。因为数据成员divisor对lambda表达式并不可见,下面的代码可以验证:

1 // 类的方法,下面无法编译,因为divisor并不在lambda捕捉的范围
2 std::function<bool(int)> getFilter() 
3 {
4     return [divisor](int value) {return value % divisor == 0; };
5 }

源代码中,lambda表达式捕获的是this指针的副本,所以原来的代码等价于:

1 std::function<bool(int)> getFilter() 
2 {
3     return [this](int value) {return value % this->divisor == 0; };
4 }

 尽管还是以值的方式捕获,但是捕获的是指针,其实相当于以引用的方式捕获了当前类对象。

lambda表达式的闭包与一个类对象绑定在一起,因为你任然有可能在类对象析构之后使用这个lambda表达式,所以类似“悬挂引用”的问题依然存在。采用默认值捕捉所有变量任然是不安全的,主要是由于无指针变量的复制,实际上按引用传值。

应用

lambda表达式可以赋值给对应类型的函数指针,但是使用函数指针并不方便。

所以在STL定义的<functional>头文件提供了一个多态的函数对象封装std::function,其类似函数指针。它可以绑定函数对象,只要参数和返回类型相同。如下面的返回一个bool且接受两个int的函数的包装器:

std::function<bool(int,int)> wrapper=[](int x,int y){return x<y;};

lambda表达式另一个更重要的应用是其可以用于函数的参数,这种方式可以实现回调函数。

1 int value=3;
2 vector<int> v{1,3,5,2,6,10};
3 int count=std::count_if(v.begin(),v.end(),[value](int x){return x>value;});

猜你喜欢

转载自blog.csdn.net/haimianjie2012/article/details/113103124