C++中的Lambda表达式

来源:互联网 发布:弹幕的json数据 编辑:程序博客网 时间:2024/05/13 23:58

在C++11中,lambda表达式(通常称为”lambda”)是一种在被调用的位置或作为参数传递给函数的位置定义匿名函数对象的简便方法。Lambda 通常用于封装传递给算法或异步方法的少量代码行。

Lambda表达式的各部分
这里写图片描述
1. Capture子句(在C++规范中也称为lambda引导)
2. 参数列表(可选)。(也称为lambda声明符)
3. 可变规范(可选)。
4. 异常规范(可选)。
5. 尾随返回类型(可选)。
6. “lambda 体”

一、Capture子句
Lambda可在其主体中引入新的变量(用 C++14),它还可以访问(或“捕获”)周边范围内的变量。Lambda以Capture子句(标准语法中的lambda引导)开头,它指定要捕获的变量以及是通过值还是引用进行捕获。有与号 (&) 前缀的变量通过引用访问,没有该前缀的变量通过值访问。

空capture子句[ ]指示lambda表达式的主体不访问封闭范围中的变量。

可以使用默认捕获模式(标准语法中的capture-default)来指示如何捕获lambda中引用的任何外部变量:[&]表示通过引用捕获引用的所有变量,而[=]表示通过值捕获它们。可以使用默认捕获模式,然后为特定变量显式指定相反的模式。例如,如果lambda体通过引用访问外部变量total并通过值访问外部变量factor,则以下 capture子句等效:

C++[&total, factor][factor, &total][&, factor][factor, &][=, &total][&total, =]

使用capture-default时,只有lambda中提及的变量才会被捕获。

如果capture子句包含&,则该capture子句的identifier中没有任何capture可采用&identifier形式。同样,如果capture子句包含 =,则该capture子句的capture不能采用=identifier形式。identifier或this在capture子句中出现的次数不能超过一次。以下代码片段给出了一些示例。

C++struct S { void f(int i); };void S::f(int i) {    [&, i]{};    // OK    [&, &i]{};   // ERROR: i preceded by & when & is the default    [=, this]{}; // ERROR: this when = is the default    [i, i]{};    // ERROR: i repeated}

capture后跟省略号是包扩展,如以下可变参数模板示例中所示:

C++template<class... Args>void f(Args... args) {    auto x = [args...] { return g(args...); };    x();}

要在类方法的正文中使用lambda表达式,请将this指针传递给Capture子句,以提供对封闭类的方法和数据成员的访问权限。

在使用capture子句时,建议你记住以下几点(尤其是使用采取多线程的lambda时):
1. 引用捕获可用于修改外部变量,而值捕获却不能实现此操作。(mutable允许修改副本,而不能修改原始项。)
2. 引用捕获会反映外部变量的更新,而值捕获却不会反映。
3. 引用捕获引入生存期依赖项,而值捕获却没有生存期依赖项。当lambda以异步方式运行时,这一点尤其重要。如果在异步lambda中通过引用捕获本地变量,该本地变量将很可能在lambda运行时消失,从而导致运行时访问冲突。

二、参数列表
除了捕获变量,lambda还可接受输入参数。参数列表(在标准语法中称为lambda声明符)是可选的,它在大多数方面类似于函数的参数列表。

auto y = [] (int first, int second){    return first + second;};

lambda表达式可以将另一个lambda表达式作为其参数。

三、可变规范
通常,lambda的函数调用运算符为const-by-value,但对mutable关键字的使用可将其取消。它不会生成可变的数据成员。利用可变规范,lambda表达式的主体可以修改通过值捕获的变量。

四、异常规范
你可以使用throw()异常规范来指示lambda表达式不会引发任何异常。

C++int main() // C4297 expected{   []() throw() { throw 5; }();}

五、返回类型
将自动推导lambda表达式的返回类型。无需使用auto关键字,除非指定尾随返回类型。trailing-return-type类似于普通方法或函数的返回类型部分。但是,返回类型必须跟在参数列表的后面,你必须在返回类型前面包含trailing-return-type关键字 ->。

如果lambda体仅包含一个返回语句或其表达式不返回值,则可以省略lambda表达式的返回类型部分。如果lambda体包含单个返回语句,编译器将从返回表达式的类型推导返回类型。否则,编译器会将返回类型推导为void。下面的代码示例片段说明了这一原则。

C++auto x1 = [](int i){ return i; }; // OK: return type is intauto x2 = []{ return{ 1, 2 }; };  // ERROR: return type is void, deducing                                   // return type from braced-init-list is not valid

lambda表达式可以生成另一个lambda表达式作为其返回值。

六、Lambda体
lambda表达式的lambda体(标准语法中的compound-statement)可包含普通方法或函数的主体可包含的任何内容。普通函数和lambda表达式的主体均可访问以下变量类型:
1. 从封闭范围捕获变量,如前所述。
2. 参数
3. 本地声明变量
4. 类数据成员(在类内部声明并且捕获 this 时)
5. 具有静态存储持续时间的任何变量(例如,全局变量)

以下示例包含通过值显式捕获变量n并通过引用隐式捕获变量m的lambda表达式:

C++#include <iostream>using namespace std;int main(){   int m = 0;   int n = 0;   [&, n] (int a) mutable { m = ++n + a; }(4);   cout << m << endl << n << endl;}
输出:50

由于变量n是通过值捕获的,因此在调用lambda表达式后,变量的值仍保持0不变。mutable规范允许在lambda中修改n。

尽管lambda表达式只能捕获具有自动存储持续时间的变量,但你可以在lambda表达式的主体中使用具有静态存储持续时间的变量。以下示例使用generate函数和lambda表达式为vector对象中的每个元素赋值。lambda表达式将修改静态变量以生成下一个元素的值。

void fillVector(vector<int>& v){    // A local static variable.    static int nextValue = 1;    generate(v.begin(), v.end(), [] { return nextValue++; });     //WARNING: this is not thread-safe and is shown for illustration only    /*     // generate 函数     template <class _ForwardIterator, class _Generator>     inline _LIBCPP_INLINE_VISIBILITY     void     generate(_ForwardIterator __first, _ForwardIterator __last, _Generator __gen)     {     for (; __first != __last; ++__first)     *__first = __gen();     }     */}

下面的代码示例使用上一示例中的fillVector函数,并添加了使用STL算法generate_n的lambda表达式的示例。该lambda表达式将vector对象的元素指派给前两个元素之和。使用了mutable关键字,以使lambda表达式的主体可以修改lambda表达式通过值捕获的外部变量x和y的副本。由于lambda表达式通过值捕获原始变量x和y,因此它们的值在lambda执行后仍为1。

#include <algorithm>#include <iostream>#include <vector>#include <string>using namespace std;template <typename C>void print(const string& s, const C& c) {    cout << s;    for (const auto& e : c) {        cout << e << " ";    }    cout << endl;}void fillVector(vector<int>& v){    // A local static variable.    static int nextValue = 1;    generate(v.begin(), v.end(), [] { return nextValue++; });    //WARNING: this is not thread-safe and is shown for illustration only}int main(){    // The number of elements in the vector.    const int elementCount = 9;    // Create a vector object with each element set to 1.    vector<int> v(elementCount, 1);    // These variables hold the previous two elements of the vector.    int x = 1;    int y = 1;    // Sets each element in the vector to the sum of the previous two elements.    generate_n(v.begin() + 2,        elementCount - 2,        [=]() mutable throw() -> int { // lambda is the 3rd parameter        // Generate current value.        int n = x + y;        // Update previous two values.        x = y;        y = n;        return n;    });    /*     // generate_n 函数     template <class _OutputIterator, class _Size, class _Generator>     inline _LIBCPP_INLINE_VISIBILITY     _OutputIterator     generate_n(_OutputIterator __first, _Size __n, _Generator __gen)     {     for (; __n > 0; ++__first, --__n)     *__first = __gen();     return __first;     }     */    print("vector v after call to generate_n() with lambda: ", v);    // Print the local variables x and y.    // The values of x and y hold their initial values because they are captured by value.    cout << "x: " << x << " y: " << y << endl;    // Fill the vector with a sequence of numbers    fillVector(v);    print("vector v after 1st call to fillVector(): ", v);    // Fill the vector with the next sequence of numbers    fillVector(v);    print("vector v after 2nd call to fillVector(): ", v);}
输出:vector v after call to generate_n() with lambda: 1 1 2 3 5 8 13 21 34x: 1 y: 1vector v after 1st call to fillVector(): 1 2 3 4 5 6 7 8 9vector v after 2nd call to fillVector(): 10 11 12 13 14 15 16 17 18

原文地址:
MSDN C++中的Lambda表达式

0 0
原创粉丝点击