C++11、14、17里的lambda表达式简介

复习的时候发现lambda表达式很有意思,以前又没有注意过,因此记录一下。

一、lambda表达式:C++11

C++11加入了lambda表达式。总的来说一个lambda表达式表达了一个函数对象。例如下面这个例子:

1 int a = 1;
2 auto out = [](int x) { cout << x << endl; };
3     // lambda表达式的本质是一个函数,用auto来判断它的类型。
4     //用[]可以声明一个lambda表达式。
5 out(a);  // 1

lambda表达式的模式就是“[](参数){函数体;}”,用auto可以把一个lambda表达式表达的函数对象存在一个变量中。

“[]”是声明lambda表达式的语句,它有一些其他的模式,使得lambda表达式可以使用外部变量(叫做lambda表达式捕获外部变量),如下:

 1 auto nxt = [=]() { cout << a + 1 << endl; };
 2     //[]中写=,表明用传值(临时变量)的方式使用外部变量。
 3     //若其中什么都没有,则不可以使用外部变量。
 4 nxt();   // 2
 5 out(a);  // 1
 6 
 7 auto ind = [&]() { cout << ++a << endl; };
 8     //[]中写&,表明用引用的方式使用外部变量。
 9 ind();   // 2
10 out(a);  // 2
11 
12 int b = 5;
13 auto mul = [b, &a]() { cout << (a *= b) << endl; };
14     //可以规定外部变量的用法,如此例,b即用传值,a即用引用。
15     //传值都应该写在引用之前。
16 mul();   // 10
17 out(a);  // 10
18 out(b);  // 5

更严格一点,lambda表达式可以被规定类型:

1 auto fnxt = [](int x) -> double { return double(x) + 0.01; };
2     //在参数列表后面写上->可以规定返回类型,否则编译器会自行判断。
3     //然而这个类型与auto所指代的类型是不同的。
4 cout << fnxt(a) << endl;  // 10.01

lambda表达式也可以不保存在变量里,作为一个临时变量。这是一个更方便的应用方法,譬如应用在STL算法里:

 1 cout << [](int x, int y) { return x + y; }(a, b) << endl;  // 15
 2     // lambda表达式当然也可以是一个临时变量
 3 
 4 int arr[5]{1, 6, 4, 3, 5};
 5 sort(arr, arr + 5, [](int x, int y) -> bool { return x < y; });
 6 for_each(arr, arr + 5, [](int x) { cout << x << " "; });
 7     //自然也可以当作函数对象传入某些STL方法
 8 cout << endl;  // 1 3 4 5 6
 9 for_each(arr, arr + 5, out);
10     /*
11     1
12     3
13     4
14     5
15     6
16     */

lambda表达式也可以作为一个返回值。这可以导致一些复杂的应用:

1 auto strAdd = [](string x) { return [=](string y) { return y + x; }; };
2     // lambda表达式是可以嵌套的。
3     //此处定义了一个结合函数,允许两个(),作用是把前一个字符串接到后一个上。
4     //第一个()返回的是一个需要一个()的lambda函数。
5     //注意在内层lambda表达式看来,外层的形参也是外部变量,因此需要传值使用。
6     // lambda表达式过多时分号是要格外注意的。
7 cout << strAdd("world!")("Hello ") << endl;  // Hello world!

二、泛型lambda:C++14

C++11里引入了如上的lambda表达式,是极其方便的,然而还有一些限制没有被考虑到。譬如,虽然lambda表达式像是一个函数类,但是却不能是一个函数类模板。

C++14里解决了这个问题(这被称为泛型lambda表达式):

1 auto Tsqr = [](auto x) { return x * x; };
2     // c++14里相对c++11添加了这一功能,允许lambda表达式用auto做参数类型。
3     //相当于lambda表达式也可以是一个模板。
4 cout << Tsqr(10) << " " << Tsqr(2.5) << endl;  // 100 6.25

上面这段代码类似于下面的形式:

1 template<class T>
2 T Tsqr (T val) { return val * val; }

三、constexpr lambda等:C++17

还有一些其它的问题在C++17里被解决。其中之一是在类里的lambda表达式的问题,它不能平白地捕获类的成员变量,比如:

1 class C {
2     int val;
3     public:
4     C():val(0){}
5     C(int a):val(a){}
6     void print(){
7         []() { cout << val << endl; }();
8     }
9 };

这段代码编译时会报下面这样的错:

work.cpp: In lambda function:
work.cpp:15:24: error: 'this' was not captured for this lambda function
         []() { cout << val << endl; }();
                        ^~~
work.cpp:15:24: error: invalid use of non-static data member 'C::val'
work.cpp:10:9: note: declared here
     int val;

报这个错的原因也很显然:传给成员函数的其实是this指针,因此在C++11、14里想完成这种工作应该这样写:

 1 class C {
 2     int val;
 3     public:
 4     C():val(0){}
 5     C(int a):val(a){}
 6     void print(){
 7         [this]() { cout << val << endl; }();
 8         //c++11、14中想要让lambda函数使用成员变量,需要传this进去
 9     }
10 };

可是这样子做又有新的问题,也就是this传入的是一个指针,这可能导致一些未知的问题。譬如当lambda表达式被调用时,this已经被销毁(在多线程编程时可能会遇到)。C++17提供了一个方案:

 1 class C {
 2     int val;
 3 
 4    public:
 5     C() : val(0) {}
 6     C(int a) : val(a) {}
 7     ~C() { cout << "destructed" << endl; }
 8     void print() {
 9         [*this]() { cout << val << endl; }();
10         // c++11、14中想要让lambda函数使用成员变量,需要传this进去。
11         // c++17中用*this表示传递一个拷贝进去。
12     }
13 };

执行下面这段代码会看到析构函数被调用了两次:

C(13).print();  // 13
    //destructed
    //destructed

C++17里另一个改变是lambda表达式现在可以被视作一个constexpr常量,只要表达式里没有用到静态变量、虚函数(多态)、try/throw/catch、new/delete。

(注:C++11之后加入的constexpr表示一个量是“可以在编译时就算出值”的常量。相比之下,const则没有这个限制,譬如函数形参里的const引用。可见constexpr是一个更严格的常量。严格地讲,constexpr是“常量”,而const是“只读”。)

譬如下面的代码:

 1 template <int N>
 2 class D {
 3     int n = N;
 4 };
 5 
 6 int x;
 7 constexpr int y = 10;
 8 auto f = [](int x) { return x + 1; };
 9 D<x> d1;     // error
10 D<y> d2;     // pass
11 D<f(7)> d3;  // c++17 : pass

以上就是C++11~17里对lambda表达式的引入和补充。

posted @ 2021-05-08 15:17  Halifuda  阅读(931)  评论(0编辑  收藏  举报