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表达式的引入和补充。