【问题标题】:What is a lambda expression in C++11?什么是 C++11 中的 lambda 表达式?
【发布时间】:2021-12-24 18:54:56
【问题描述】:

什么是 C++11 中的 lambda 表达式?我什么时候用一个?他们解决了哪类问题在引入之前无法解决?

几个例子和用例会很有用。

【问题讨论】:

  • 我见过一个 lambda 非常有用的案例:我的一位同事正在编写具有数百万次迭代的代码来解决空间优化问题。该算法在使用 lambda 时比使用适当的函数要快得多!编译器为 Visual C++ 2013。
  • 这是另一个非常好的参考资料,它很好地解释了 C++ 中的 lambda 表达式:Microsoft.com: Lambda expressions in C++。我特别喜欢它很好地解释了 lambda 表达式的 部分,特别是:capture 子句、参数列表、尾随返回类型和 lambda 主体。跨度>

标签: c++ lambda c++11 c++-faq


【解决方案1】:

问题

C++ 包含有用的泛型函数,例如 std::for_eachstd::transform,它们非常方便。不幸的是,它们使用起来也很麻烦,特别是如果您要应用的 functor 是特定功能所独有的。

#include <algorithm>
#include <vector>

namespace {
  struct f {
    void operator()(int) {
      // do something
    }
  };
}

void func(std::vector<int>& v) {
  f f;
  std::for_each(v.begin(), v.end(), f);
}

如果您只使用一次f,并且在那个特定的地方使用一次,那么编写整个课程只是为了做一些微不足道的事情似乎有点过头了。

在 C++03 中,您可能会想编写如下内容,以保持函子本地化:

void func2(std::vector<int>& v) {
  struct {
    void operator()(int) {
       // do something
    }
  } f;
  std::for_each(v.begin(), v.end(), f);
}

但是这是不允许的,f 不能传递给 C++03 中的 template 函数。

新的解决方案

C++11 引入了 lambda,允许您编写一个内联的匿名函子来替换 struct f。对于小的简单示例,这可以更清晰地阅读(它将所有内容都保存在一个地方)并且可能更易于维护,例如以最简单的形式:

void func3(std::vector<int>& v) {
  std::for_each(v.begin(), v.end(), [](int) { /* do something here*/ });
}

Lambda 函数只是匿名函子的语法糖。

返回类型

在简单的情况下,会为您推导出 lambda 的返回类型,例如:

void func4(std::vector<double>& v) {
  std::transform(v.begin(), v.end(), v.begin(),
                 [](double d) { return d < 0.00001 ? 0 : d; }
                 );
}

但是,当您开始编写更复杂的 lambda 表达式时,您很快就会遇到编译器无法推断返回类型的情况,例如:

void func4(std::vector<double>& v) {
    std::transform(v.begin(), v.end(), v.begin(),
        [](double d) {
            if (d < 0.0001) {
                return 0;
            } else {
                return d;
            }
        });
}

要解决此问题,您可以使用 -&gt; T 显式指定 lambda 函数的返回类型:

void func4(std::vector<double>& v) {
    std::transform(v.begin(), v.end(), v.begin(),
        [](double d) -> double {
            if (d < 0.0001) {
                return 0;
            } else {
                return d;
            }
        });
}

“捕获”变量

到目前为止,除了传递给其中的 lambda 之外,我们还没有使用任何其他变量,但我们也可以在 lambda 中使用其他变量。如果你想访问其他变量,你可以使用捕获子句(表达式的[]),到目前为止在这些示例中没有使用,例如:

void func5(std::vector<double>& v, const double& epsilon) {
    std::transform(v.begin(), v.end(), v.begin(),
        [epsilon](double d) -> double {
            if (d < epsilon) {
                return 0;
            } else {
                return d;
            }
        });
}

您可以通过引用和值来捕获,您可以分别使用&amp;= 指定:

  • [&amp;epsilon, zeta] 通过引用捕获 epsilon,按值捕获 zeta
  • [&amp;] 通过引用捕获 lambda 中使用的所有变量
  • [=] 按值捕获 lambda 中使用的所有变量
  • [&amp;, epsilon] 通过引用捕获 lambda 中使用的所有变量,但按值捕获 epsilon
  • [=, &amp;epsilon] 通过值捕获 lambda 中使用的所有变量,但通过引用捕获 epsilon

生成的operator()默认为const,默认访问时捕获为const。这具有相同输入的每次调用都会产生相同结果的效果,但是您可以mark the lambda as mutable 请求产生的operator() 不是const

【讨论】:

  • @Yakk 你被困住了。没有捕获的 lambda 具有到函数类型指针的隐式转换。转换函数为const always...
  • 有趣 - 我最初认为 lambda 是匿名的函数而不是仿函数,并且对捕获的工作原理感到困惑。
  • 如果你想在你的程序中使用 lambdas 作为变量,你可以使用:std::function&lt;double(int, bool)&gt; f = [](int a, bool b) -&gt; double { ... }; 但通常,我们让编译器推断类型:auto f = [](int a, bool b) -&gt; double { ... };(别忘了@987654354 @)
  • 我想不是每个人都明白为什么return d &lt; 0.00001 ? 0 : d; 在其中一个操作数是整数常量时保证返回双精度(这是因为 ?: 运算符的隐式提升规则,其中第二个和第三个操作数通过通常的算术转换相互平衡,无论选择哪一个)。更改为 0.0 : d 可能会使示例更易于理解。
  • @MohammadMamunHossain 使用 std::array 数组而不是原始数组,然后它就变得微不足道了。 (无论如何,在大多数情况下,这在 C++ 中是个好建议)
【解决方案2】:

C++ 11 引入了 lambda 表达式以允许我们编写一个内联函数,该函数可用于短的 sn-ps 代码

[ capture clause ] (parameters) -> return-type
{
   definition of method
}

通常 lambda 表达式中的 return-type 由编译器本身评估,我们不需要明确指定 -> return-type 部分可以忽略,但在一些复杂的情况下,如条件语句,编译器无法使返回类型,我们需要指定它。

// C++ program to demonstrate lambda expression in C++
#include <bits/stdc++.h>
using namespace std;

// Function to print vector
void printVector(vector<int> v)
{
    // lambda expression to print vector
    for_each(v.begin(), v.end(), [](int i)
    {
        std::cout << i << " ";
    });
    cout << endl;
}

int main()
{
    vector<int> v {4, 1, 3, 5, 2, 3, 1, 7};

    printVector(v);

    // below snippet find first number greater than 4
    // find_if searches for an element for which
    // function(third argument) returns true
    vector<int>:: iterator p = find_if(v.begin(), v.end(), [](int i)
    {
        return i > 4;
    });
    cout << "First number greater than 4 is : " << *p << endl;


    // function to sort vector, lambda expression is for sorting in
    // non-decreasing order Compiler can make out return type as
    // bool, but shown here just for explanation
    sort(v.begin(), v.end(), [](const int& a, const int& b) -> bool
    {
        return a > b;
    });

    printVector(v);

    // function to count numbers greater than or equal to 5
    int count_5 = count_if(v.begin(), v.end(), [](int a)
    {
        return (a >= 5);
    });
    cout << "The number of elements greater than or equal to 5 is : "
        << count_5 << endl;

    // function for removing duplicate element (after sorting all
    // duplicate comes together)
    p = unique(v.begin(), v.end(), [](int a, int b)
    {
        return a == b;
    });

    // resizing vector to make size equal to total different number
    v.resize(distance(v.begin(), p));
    printVector(v);

    // accumulate function accumulate the container on the basis of
    // function provided as third argument
    int arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    int f = accumulate(arr, arr + 10, 1, [](int i, int j)
    {
        return i * j;
    });

    cout << "Factorial of 10 is : " << f << endl;

    //   We can also access function by storing this into variable
    auto square = [](int i)
    {
        return i * i;
    };

    cout << "Square of 5 is : " << square(5) << endl;
}

输出

4 1 3 5 2 3 1 7
First number greater than 4 is : 5
7 5 4 3 3 2 1 1
The number of elements greater than or equal to 5 is : 2
7 5 4 3 2 1
Factorial of 10 is : 3628800
Square of 5 is : 25

通过从封闭范围访问变量,lambda 表达式可以比普通函数更强大。我们可以通过三种方式从封闭范围中捕获外部变量:

  • 通过引用捕获
  • 按值捕获
  • 两者都捕获(混合捕获)

用于捕获变量的语法:

  • [&] : 通过引用捕获所有外部变量
  • [=] : 按值捕获所有外部变量
  • [a, &b] : 按值捕获 a,按引用捕获 b 带有空捕获子句 [ ] 的 lambda 只能访问它的本地变量。
    #include <bits/stdc++.h>
    using namespace std;
    
    int main()
    {
        vector<int> v1 = {3, 1, 7, 9};
        vector<int> v2 = {10, 2, 7, 16, 9};
    
        // access v1 and v2 by reference
        auto pushinto = [&] (int m)
        {
            v1.push_back(m);
            v2.push_back(m);
        };
    
        // it pushes 20 in both v1 and v2
        pushinto(20);
    
        // access v1 by copy
        [v1]()
        {
            for (auto p = v1.begin(); p != v1.end(); p++)
            {
                cout << *p << " ";
            }
        };
    
        int N = 5;
    
        // below snippet find first number greater than N
        // [N] denotes, can access only N by value
        vector<int>:: iterator p = find_if(v1.begin(), v1.end(), [N](int i)
        {
            return i > N;
        });
    
        cout << "First number greater than 5 is : " << *p << endl;
    
        // function to count numbers greater than or equal to N
        // [=] denotes, can access all variable
        int count_N = count_if(v1.begin(), v1.end(), [=](int a)
        {
            return (a >= N);
        });
    
        cout << "The number of elements greater than or equal to 5 is : "
            << count_N << endl;
    }

输出:

   First number greater than 5 is : 7
   The number of elements greater than or equal to 5 is : 3

【讨论】:

    【解决方案3】:

    c++ 中的 lambda 被视为“随时可用的函数”。 是的,它实际上是在旅途中,你定义它;用它;并且随着父函数作用域的完成,lambda 函数就消失了。

    c++ 在 c++ 11 中引入了它,每个人都开始在每个可能的地方使用它。 示例和什么是 lambda 可以在这里找到https://en.cppreference.com/w/cpp/language/lambda

    我将描述哪些不存在,但对于每个 c++ 程序员来说都是必不可少的

    Lambda 并不意味着在任何地方都可以使用,并且每个函数都不能用 lambda 代替。与正常功能相比,它也不是最快的。因为它有一些需要 lambda 处理的开销。

    在某些情况下,它肯定有助于减少行数。 它基本上可以用于在同一函数中被调用一次或多次的代码部分,并且在其他任何地方都不需要该代码,因此您可以为其创建独立函数。

    以下是 lambda 的基本示例以及后台发生的情况。

    用户代码:

    int main()
    {
      // Lambda & auto
      int member=10;
      auto endGame = [=](int a, int b){ return a+b+member;};
    
      endGame(4,5);
    
      return 0;
    
    }
    

    编译如何扩展它:

    int main()
    {
      int member = 10;
    
      class __lambda_6_18
      {
        int member;
        public: 
        inline /*constexpr */ int operator()(int a, int b) const
        {
          return a + b + member;
        }
    
        public: __lambda_6_18(int _member)
        : member{_member}
        {}
    
      };
    
      __lambda_6_18 endGame = __lambda_6_18{member};
      endGame.operator()(4, 5);
    
      return 0;
    }
    

    所以你可以看到,当你使用它时它会增加什么样的开销。 所以在任何地方都使用它们并不是一个好主意。 可以在适用的地方使用。

    【讨论】:

    • 是的,它实际上是在旅途中,你定义它;用它;并且随着父函数作用域完成,lambda 函数消失了 .. 如果函数将 lambda 返回给调用者怎么办?
    • 与普通功能相比,它也不是最快的。因为它有一些需要 lambda 处理的开销。 你有没有实际上运行过任何基准来支持这个声明?相反,lambda + 模板通常可以生成最快的代码。
    【解决方案4】:

    答案

    问:什么是 C++11 中的 lambda 表达式?

    答:在底层,它是一个自动生成的类的对象,重载了 operator() const。这样的对象称为 closure 并由编译器创建。 这个“闭包”概念与 C++11 中的绑定概念很接近。 但 lambda 通常会生成更好的代码。并且通过闭包调用允许完全内联。

    问:我什么时候用?

    A:定义“简单和小逻辑”并要求编译器从上一个问题执行生成。你给编译器一些你想在 operator() 中的表达式。所有其他东西编译器都会生成给你。

    问:他们解决了哪类问题在引入之前无法解决?

    A:它是某种语法糖,例如运算符重载,而不是用于自定义 add、subrtact 操作的函数......但它节省了更多行不需要的代码来包装 1-3 行真实的某些类的逻辑等等!有些工程师认为,如果行数越少,出错的机会就越小(我也是这么认为的)

    使用示例

    auto x = [=](int arg1){printf("%i", arg1); };
    void(*f)(int) = x;
    f(1);
    x(1);
    

    关于 lambdas 的额外内容,未包含在问题中。如果您不感兴趣,请忽略此部分

    1.捕获的值。你可以捕捉到什么

    1.1。您可以在 lambda 中引用具有静态存储持续时间的变量。他们都被俘虏了。

    1.2。您可以将 lambda 用于“按值”捕获值。在这种情况下,捕获的变量将被复制到函数对象(闭包)。

    [captureVar1,captureVar2](int arg1){}
    

    1.3。您可以捕获作为参考。 & -- 在这种情况下是指引用,而不是指针。

       [&captureVar1,&captureVar2](int arg1){}
    

    1.4。它存在按值或按引用捕获所有非静态变量的符号

      [=](int arg1){} // capture all not-static vars by value
    
      [&](int arg1){} // capture all not-static vars by reference
    

    1.5。它存在按值或按引用捕获所有非静态变量并指定 smth 的符号。更多的。 例子: 按值捕获所有非静态变量,但通过引用捕获 Param2

    [=,&Param2](int arg1){} 
    

    通过引用捕获所有非静态变量,但通过值捕获 Param2

    [&,Param2](int arg1){} 
    

    2.返回类型推导

    2.1。如果 lambda 是一个表达式,则可以推导出 Lambda 返回类型。或者您可以明确指定它。

    [=](int arg1)->trailing_return_type{return trailing_return_type();}
    

    如果 lambda 有多个表达式,则返回类型必须通过尾随返回类型指定。 此外,类似的语法可以应用于自动函数和成员函数

    3.捕获的值。你无法捕捉到的东西

    3.1。只能捕获局部变量,不能捕获对象的成员变量。

    4. Сonversions

    4.1 !! Lambda 不是函数指针,也不是匿名函数,但是 capture-less lambda 可以隐式转换为函数指针。

    附言

    1. 有关 lambda 语法信息的更多信息可以在 Programming Language C++ #337, 2012-01-16, 5.1.2 的工作草案中找到。 Lambda 表达式,第 88 页

    2. 在 C++14 中添加了名为“init capture”的额外功能。它允许对闭包数据成员进行任意声明:

      auto toFloat = [](int value) { return float(value);};
      auto interpolate = [min = toFloat(0), max = toFloat(255)](int value)->float { return (value - min) / (max - min);};
      

    【讨论】:

    • 这个[&amp;,=Param2](int arg1){} 似乎不是有效的语法。正确的形式是[&amp;,Param2](int arg1){}
    • 谢谢。首先我尝试编译这个sn-p。捕获列表中允许的修饰符似乎很奇怪的不对称 // g++ -std=c++11 main.cpp -o test_bin; ./test_bin #include int main() { #if 1 { int param = 0;自动 f=[=,&param](int arg1) 可变 {param = arg1;}; f(111); printf("%i\n", 参数); } #endif #if 0 { int 参数 = 0;自动 f=[&,=param](int arg1) 可变 {param = arg1;}; f(111); printf("%i\n", 参数); } #endif 返回 0; }
    • 在评论中不支持新行。然后我打开了 5.1.2 Lambda 表达式,第 88 页,“工作草案,C++ 编程语言标准”,文档编号:#337,2012-01-16。并研究了语法语法。你是对的。不存在通过“=arg”捕获之类的东西
    • 非常感谢,在描述中修复它并获得新知识 w.r.t.给它。
    【解决方案5】:

    嗯,我发现的一个实际用途是减少样板代码。例如:

    void process_z_vec(vector<int>& vec)
    {
      auto print_2d = [](const vector<int>& board, int bsize)
      {
        for(int i = 0; i<bsize; i++)
        {
          for(int j=0; j<bsize; j++)
          {
            cout << board[bsize*i+j] << " ";
          }
          cout << "\n";
        }
      };
      // Do sth with the vec.
      print_2d(vec,x_size);
      // Do sth else with the vec.
      print_2d(vec,y_size);
      //... 
    }
    

    如果没有 lambda,您可能需要针对不同的 bsize 情况做一些事情。当然你可以创建一个函数,但是如果你想将使用限制在灵魂用户函数的范围内呢? lambda 的性质满足了这个要求,我在这种情况下使用它。

    【讨论】:

    【解决方案6】:

    C++作者Bjarne Stroustrup在他的书***The C++ Programming Language***第11章(ISBN-13: 978-0321563842)中给出了对lambda expression的最佳解释之一:

    What is a lambda expression?

    lambda 表达式,有时也称为 lambda 功能或(严格来说不正确,但通俗地说)作为 lambda,是定义和使用匿名函数对象的简化符号。不是用 operator() 定义命名类,而是稍后创建该类的对象,最后 调用它,我们可以使用简写。

    When would I use one?

    当我们想要将操作作为 算法的论据。在图形用户界面的上下文中 (以及其他地方),此类操作通常称为回调

    What class of problem do they solve that wasn't possible prior to their introduction?

    在这里,我想使用 lambda 表达式完成的每个操作都可以在没有它们的情况下解决,但需要更多的代码和更大的复杂性。 Lambda 表达式这是优化代码的方式,也是使其更具吸引力的方式。像 Stroustup 一样悲伤:

    有效的优化方法

    Some examples

    通过 lambda 表达式

    void print_modulo(const vector<int>& v, ostream& os, int m) // output v[i] to os if v[i]%m==0
    {
        for_each(begin(v),end(v),
            [&os,m](int x) { 
               if (x%m==0) os << x << '\n';
             });
    }
    

    或通过函数

    class Modulo_print {
             ostream& os; // members to hold the capture list int m;
         public:
             Modulo_print(ostream& s, int mm) :os(s), m(mm) {} 
             void operator()(int x) const
               { 
                 if (x%m==0) os << x << '\n'; 
               }
    };
    

    甚至

    void print_modulo(const vector<int>& v, ostream& os, int m) 
         // output v[i] to os if v[i]%m==0
    {
        class Modulo_print {
            ostream& os; // members to hold the capture list
            int m; 
            public:
               Modulo_print (ostream& s, int mm) :os(s), m(mm) {}
               void operator()(int x) const
               { 
                   if (x%m==0) os << x << '\n';
               }
         };
         for_each(begin(v),end(v),Modulo_print{os,m}); 
    }
    

    如果您需要,您可以命名lambda expression,如下所示:

    void print_modulo(const vector<int>& v, ostream& os, int m)
        // output v[i] to os if v[i]%m==0
    {
          auto Modulo_print = [&os,m] (int x) { if (x%m==0) os << x << '\n'; };
          for_each(begin(v),end(v),Modulo_print);
     }
    

    或者假设另一个简单的样本

    void TestFunctions::simpleLambda() {
        bool sensitive = true;
        std::vector<int> v = std::vector<int>({1,33,3,4,5,6,7});
    
        sort(v.begin(),v.end(),
             [sensitive](int x, int y) {
                 printf("\n%i\n",  x < y);
                 return sensitive ? x < y : abs(x) < abs(y);
             });
    
    
        printf("sorted");
        for_each(v.begin(), v.end(),
                 [](int x) {
                     printf("x - %i;", x);
                 }
                 );
    }
    

    将生成下一个

    0

    1

    0

    1

    0

    1

    0

    1

    0

    1

    0 排序x - 1;x - 3;x - 4;x - 5;x - 6;x - 7;x - 33;

    [] - 这是捕获列表或lambda introducer:如果lambdas 不需要访问其本地环境,我们可以使用它。

    引用书:

    lambda 表达式的第一个字符始终是 [。一个拉姆达 介绍人可以采取多种形式:

    []:一个空的捕获列表。这 意味着不能使用来自周围上下文的本地名称 在 lambda 体内。对于这样的 lambda 表达式,数据是从 参数或来自非局部变量。

    [&]:隐式捕获 参考。可以使用所有本地名称。所有局部变量都是 通过引用访问。

    [=]:按值隐式捕获。所有本地 可以使用名称。所有名称均指局部变量的副本 在调用 lambda 表达式时获取。

    [capture-list]: 显式捕获;捕获列表是要通过引用或值捕获(即存储在对象中)的局部变量的名称列表。名称以 & 开头的变量被捕获 参考。其他变量按值捕获。捕获列表可以 还包含 this 和名称后跟 ... 作为元素。

    [&, capture-list]:通过引用隐式捕获列表中未提及名称的所有局部变量。捕获列表可以包含它。列出的名称前面不能有 &。中命名的变量 捕获列表按值捕获。

    [=, capture-list]:按值隐式捕获列表中未提及名称的所有局部变量。捕获列表不能包含此内容。列出的名称必须以 & 开头。捕获列表中命名的变量是通过引用捕获的。

    请注意,以 & 开头的本地名称总是被 引用和前面不带 & 的本地名称总是被 价值。只有通过引用捕获才允许修改变量 调用环境。

    Additional

    Lambda expression 格式

    其他参考资料:

    【讨论】:

    • 很好的解释。使用基于范围的 for 循环,可以避免 lambda 并缩短代码 for (int x : v) { if (x % m == 0) os &lt;&lt; x &lt;&lt; '\n';}
    【解决方案7】:

    Lambda 表达式通常用于封装算法,以便将它们传递给另一个函数。但是,可以在定义后立即执行 lambda

    [&](){ ...your code... }(); // immediately executed lambda expression
    

    在功能上等同于

    { ...your code... } // simple code block
    

    这使得 lambda 表达式成为重构复杂函数的强大工具。您首先将代码部分包装在 lambda 函数中,如上所示。然后可以在每个步骤之后通过中间测试逐步执行显式参数化过程。将代码块完全参数化后(如删除 &amp; 所示),您可以将代码移动到外部位置并使其成为正常功能。

    同样,您可以使用 lambda 表达式根据算法的结果初始化变量...

    int a = []( int b ){ int r=1; while (b>0) r*=b--; return r; }(5); // 5!
    

    作为划分程序逻辑的一种方式,您甚至会发现将 lambda 表达式作为参数传递给另一个 lambda 表达式很有用...

    [&]( std::function<void()> algorithm ) // wrapper section
       {
       ...your wrapper code...
       algorithm();
       ...your wrapper code...
       }
    ([&]() // algorithm section
       {
       ...your algorithm code...
       });
    

    Lambda 表达式还允许您创建命名nested functions,这是避免重复逻辑的便捷方式。将非平凡函数作为参数传递给另一个函数时,使用命名 lambdas 也往往更容易(与匿名内联 lambdas 相比)。 注意:不要忘记右花括号后的分号。

    auto algorithm = [&]( double x, double m, double b ) -> double
       {
       return m*x+b;
       };
    
    int a=algorithm(1,2,3), b=algorithm(4,5,6);
    

    如果后续分析显示函数对象的初始化开销很大,您可能会选择将其重写为普通函数。

    【讨论】:

    • 您是否意识到这个问题是在 1.5 年前提出的,而上一次活动几乎是 1 年前?不管怎样,你贡献了一些我以前从未见过的有趣想法!
    • 感谢同时定义和执行提示!我认为值得注意的是,这可以作为if 语句的条件:if ([i]{ for (char j : i) if (!isspace(j)) return false ; return true ; }()) // i is all whitespace,假设istd::string
    • 所以下面是合法的表达方式:[](){}();.
    • 呃! Python 的(lambda: None)() 语法更加清晰。
    • @nobar - 你是对的,我打错了。这是合法的(我这次测试了)main() {{{{((([](){{}}())));}}}}
    【解决方案8】:

    它解决了一个问题:Code simpler than lambda for a call in constructor that uses an output parameter function for initializing a const member

    你可以初始化你的类的一个 const 成员,调用一个函数来设置它的值,把它的输出作为输出参数返回。

    【讨论】:

    • 这也可以用一个简单的函数来完成,这甚至是你链接到的问题的公认答案所说的。
    【解决方案9】:

    lambda 函数是您在线创建的匿名函数。正如一些人解释的那样,它可以捕获变量(例如http://www.stroustrup.com/C++11FAQ.html#lambda),但有一些限制。比如有这样的回调接口,

    void apply(void (*f)(int)) {
        f(10);
        f(20);
        f(30);
    }
    

    您可以当场编写一个函数来使用它,就像下面传递给应用的那个:

    int col=0;
    void output() {
        apply([](int data) {
            cout << data << ((++col % 10) ? ' ' : '\n');
        });
    }
    

    但你不能这样做:

    void output(int n) {
        int col=0;
        apply([&col,n](int data) {
            cout << data << ((++col % 10) ? ' ' : '\n');
        });
    }
    

    由于 C++11 标准的限制。如果要使用捕获,则必须依赖库和

    #include <functional> 
    

    (或其他一些 STL 库,如间接获取它的算法),然后使用 std::function 而不是像这样将普通函数作为参数传递:

    #include <functional>
    void apply(std::function<void(int)> f) {
        f(10);
        f(20);
        f(30);
    }
    void output(int width) {
        int col;
        apply([width,&col](int data) {
            cout << data << ((++col % width) ? ' ' : '\n');
        });
    }
    

    【讨论】:

    • 原因是,如果 lambda 没有捕获,它只能转换为函数指针。如果apply 是一个接受函子的模板,它就可以工作
    • 但问题是,如果 apply 是一个现有的接口,您可能无法像普通的旧函数那样声明它。该标准可以设计为允许在每次执行此类 lambda 表达式时生成普通旧函数的新实例,并生成对捕获变量的硬编码引用。似乎在编译时生成了一个 lambda 函数。还有其他后果。例如,如果你声明一个静态变量,即使你重新计算 lambda 表达式,你也不会得到一个新的静态变量。
    • 函数指针通常是用来保存的,而 lambdas 捕获可能会超出范围。只有无捕获的 lambda 转换为函数指针是设计使然
    • 您仍然需要注意堆栈变量因同样的原因被释放。请参阅blogs.msdn.com/b/nativeconcurrency/archive/2012/01/29/… 我使用 output 和 apply 编写的示例是这样编写的,以便如果允许并使用函数指针,它们也可以正常工作。 col 保持分配状态,直到所有来自 apply 的函数调用都完成。您将如何重写此代码以使用现有的应用接口工作?你最终会使用全局变量还是静态变量,还是对代码进行一些更模糊的转换?
    • 或者您可能只是表示 lambda 表达式是右值,因此是临时的,但代码保持不变(单例/静态),以便将来可以调用它。在这种情况下,也许函数应该保持分配状态,只要它的堆栈分配捕获保持分配状态。当然,如果在循环中分配函数的许多变体,则展开它可能会变得混乱。
    【解决方案10】:

    什么是 lambda 函数?

    lambda 函数的 C++ 概念起源于 lambda 演算和函数式编程。 lambda 是一个未命名的函数,对于无法重用且不值得命名的短 sn-ps 代码很有用(在实际编程中,而不是理论上)。

    在 C++ 中,lambda 函数是这样定义的

    []() { } // barebone lambda
    

    或者在它的所有荣耀中

    []() mutable -> T { } // T is the return type, still lacking throw()
    

    [] 是捕获列表,() 是参数列表,{} 是函数体。

    捕获列表

    捕获列表定义了 lambda 外部的哪些内容应该在函数体内可用以及如何可用。 它可以是:

    1. 一个值:[x]
    2. 参考 [&x]
    3. 当前范围内的任何变量都通过引用 [&]
    4. 与 3 相同,但按值 [=]

    您可以在逗号分隔的列表[x, &amp;y] 中混合上述任何一种。

    参数列表

    参数列表与任何其他 C++ 函数中的相同。

    函数体

    实际调用 lambda 时将执行的代码。

    返回类型扣除

    如果 lambda 只有一个 return 语句,则返回类型可以省略,并具有 decltype(return_statement) 的隐式类型。

    可变

    如果 lambda 被标记为可变(例如 []() mutable { }),则允许对已按值捕获的值进行变异。

    用例

    由 ISO 标准定义的库极大地受益于 lambda,并提高了可用性,因为现在用户不必在某些可访问范围内使用小型仿函数来弄乱他们的代码。

    C++14

    在 C++14 中,lambda 已被各种提议扩展。

    初始化的 Lambda 捕获

    现在可以使用= 初始化捕获列表的元素。这允许重命名变量并通过移动来捕获。取自标准的示例:

    int x = 4;
    auto y = [&r = x, x = x+1]()->int {
                r += 2;
                return x+2;
             }();  // Updates ::x to 6, and initializes y to 7.
    

    还有一个取自维基百科,展示了如何使用std::move 进行捕获:

    auto ptr = std::make_unique<int>(10); // See below for std::make_unique
    auto lambda = [ptr = std::move(ptr)] {return *ptr;};
    

    通用 Lambdas

    Lambdas 现在可以是通用的(auto 将等同于 T 在这里如果 T 是周围范围内某处的类型模板参数):

    auto lambda = [](auto x, auto y) {return x + y;};
    

    改进的返回类型扣除

    C++14 允许为每个函数推导出返回类型,并且不将其限制为 return expression; 形式的函数。这也扩展到 lambdas。

    【讨论】:

    • 在上面的初始化 lambda 捕获示例中,为什么用 (); 结束lamba 函数?这看起来像 [](){}();代替 [](){};。 x 的值也不应该是 5 吗?
    • @RamakrishnanKannan: 1) () 在定义它之后立即调用 lambda 并给 y 其返回值。变量 y 是一个整数,而不是 lambda。 2) 不,x=5 是 lambda 的局部变量(按值捕获,恰好与外部范围变量 x 同名),然后返回 x+2 = 5+2。外部变量 x 的重新分配通过引用 r:r = &amp;x; r += 2; 发生,但这发生在原始值 4 上。
    • 嘿,说any variable currently in scope,是什么意思?这意味着全局捕获所有全局变量以及该函数中的任何局部变量?
    猜你喜欢
    相关资源
    最近更新 更多