在lambda函数语法中,'捕获列表的目的是什么?服务?

时间:2012-03-04 10:23:34

标签: c++ c++11 lambda

取自this question的答案,例如,这是一个计算std::vector中元素总和的代码:

std::for_each(
    vector.begin(),
    vector.end(),
    [&](int n) {
        sum_of_elems += n;
    }
);

我理解 lambda函数只是无名函数。

我理解 lambda函数语法as explained here

我不明白为什么lambda函数需要捕获列表,而普通函数则不需要。

  1. 捕获列表提供了哪些额外信息?
  2. 为什么普通功能不需要这些信息?
  3. lambda的功能不仅仅是无名功能吗?

5 个答案:

答案 0 :(得分:25)

从您提供的语法链接中,捕获列表“定义了lambda外部应该在函数体内可用的内容以及如何”

普通功能可以通过以下几种方式使用外部数据:

  1. 静态字段
  2. 实例字段
  3. 参数(包括参考参数)
  4. 全局
  5. Lambda添加了在另一个中拥有一个未命名函数的功能。然后lambda可以使用您指定的值。与普通函数不同,这可以包括来自外部函数的局部变量。

    正如答案所说,您还可以指定要捕获的方式。 awoodland在another answer给出了一些例子。例如,您可以通过引用(如引用参数)捕获一个外部变量,并通过值捕获所有其他外部变量:

    [=, &epsilon]
    

    编辑:

    区分签名和lambda内部使用的内容非常重要。 lambda的签名是参数类型的有序列表,加上返回值的类型。

    例如,一元函数接受特定类型的单个值,并返回另一种类型的值。

    但是,在内部它可以使用其他值。作为一个简单的例子:

    [x, y](int z) -> int 
    {
       return x + y - z;
    }
    

    lambda的调用者只知道它需要int并返回int。但是,在内部它恰好按值使用另外两个变量。

答案 1 :(得分:19)

我们试图解决的基本问题是某些算法期望只接受一组特定参数的函数(在您的示例中为一个int)。但是,我们希望函数能够操作或检查其他一些对象,可能是这样的:

void what_we_want(int n, std::set<int> const & conditions, int & total)
{
    if (conditions.find(n) != conditions.end()) { total += n; }
}

但是,我们所有允许给出的算法都是像void f(int)这样的函数。那么我们把其他数据放在哪里呢?

您可以将其他数据保存在全局变量中,也可以按照传统的C ++方法编写仿函数:

struct what_we_must_write
{
    what_we_must_write(std::set<int> const & s, int & t)
    : conditions(s), total(t)
    {   }

    void operator()(int n)
    {
        if (conditions.find(n) != conditions.end()) { total += n; }
    }
private:
   std::set<int> const & conditions;
   int & total;
};

现在我们可以使用适当初始化的仿函数调用算法:

std::set<int> conditions;
int total;

for_each(v.begin(), v.end(), what_we_must_write(conditions, total));

最后,闭包对象(由 lambda表达式描述)就是:编写仿函数的简便方法。上述仿函数的等价物是lambda

auto what_we_get = [&conditions, &total](int n) -> void {
    if (condiditons.find(n) != conditions.end()) { total += n; } };

短手捕获列表[=][&]只捕获“所有内容”(分别通过值或引用),这意味着编译器会为您找出具体的捕获列表(它不会实际上把所有放入闭包对象中,但只放入你需要的东西。)

因此,简而言之:没有捕获的闭包对象就像一个自由函数,带有捕获的闭包就像一个带有适当定义和初始化私有成员的仿函数对象对象。

答案 2 :(得分:9)

将lambda表达式视为具有()运算符的对象,而不仅仅是一个函数,这可能更好。 lambda“object”可以包含在lambda构造时记住(或“捕获”)lambda变量的字段,稍后在lambda执行时使用

捕获列表只是这些字段的声明。

(您甚至不需要自己指定捕获列表 - [&][=]语法指示编译器根据外部的变量自动确定捕获列表范围用在lambda体中。)

普通函数不能包含状态 - 它不能一次“记住”参数以便在另一个函数中使用。一个lambda可以。一个手动制作的类,用户实现的()运算符(又名“functor”)也可以,但语法上不太方便。

答案 3 :(得分:3)

考虑一下:

std::function<int()>
make_generator(int const& i)
{
    return [i] { return i; };
}

// could be used like so:
int i = 42;
auto g0 = make_generator(i);
i = 121;
auto g1 = make_generator(i);
i = 0;

assert( g0() == 42 );
assert( g1() == 121 );

请注意,在这种情况下,已创建的两个生成器都有自己的i。这不是您可以使用普通函数重新创建的,因此不会使用捕获列表的原因。捕获列表解决了funarg problem的一个版本。

  

lambda的功能不仅仅是无名函数吗?

这是一个非常聪明的问题。 lambda表达式创建的内容实际上比常规函数更强大:它们是closures(而标准确实引用lambda表达式创建为'闭包对象'的对象)。简而言之,闭包是一个与绑定范围相结合的函数。 C ++语法选择以熟悉的形式表示函数位(带有函数体的延迟返回类型的参数列表,某些部分是可选的),而捕获列表是指定哪个局部变量将参与绑定范围的语法(非本地)变量是自动引入的。)

请注意,其他带闭包的语言通常没有类似于C ++捕获列表的构造。由于其内存模型(本地变量只与本地范围一样存在),C ++已经对捕获列表进行了设计选择,并且它的理念是不为你不使用的内容付费(如果局部变量现在自动生存更长时间“在每一个案例中都可能不会被捕获。”

答案 4 :(得分:1)

  

lambda的功能不仅仅是无名函数吗?

YES!除了无名之外,他们可以引用词汇封闭范围中的变量。在你的例子中,一个例子是sum_of_elems变量(它既不是参数也不是我想的全局变量)。 C ++中的普通函数不能这样做。

  

捕获列表提供了哪些额外信息?

捕获列表提供

  1. 使用
  2. 捕获的变量列表
  3. 信息如何被捕获(通过引用/按值)
  4. 在其他(例如,功能)语言中,这不是必需的,因为它们总是以一种方式引用值(例如,如果值是不可变的,则捕获将按值进行;其他可能性是所有内容都是变量在堆上所以一切都是通过参考捕获的,你不必担心它的生命周期等)。在C ++中,你必须指定它来在引用之间进行选择(可以在外部更改变量,但是当lambda比变量更长时会爆炸)和值(所有变化都在lambda内部隔离,但是会和lambda一样长 - 基本上,它将是表示lambda的结构中的一个字段。

    您可以使用 capture-default 符号使编译器捕获所有需要的变量,该符号仅指定默认捕获模式(在您的情况下:& =&gt;参考; =将是有价值的)。在这种情况下,基本上捕获来自外部范围的lambda中引用的所有变量。