C ++ lambdas是真正的闭包吗?通过引用捕获

时间:2016-11-23 09:54:04

标签: c++ lambda c++14

在下面的代码中,我创建了一个lambda,它通过引用捕获局部变量。请注意,它是一个指针,因此,如果C ++ lambdas是真正的闭包,它应该在创建lambda的函数的生命周期中存活。

然而,当我再次调用它时,它不是创建一个新的局部变量(一个新的环境),而是重用与之前相同的东西,实际上,它捕获与之前完全相同的指针。

这似乎不对。或者,C ++ lambdas不是真正的闭包,还是我的代码不正确?

感谢您的帮助

#include <iostream>
#include <functional>
#include <memory>

std::function<int()> create_counter()
{
    std::shared_ptr<int> counter = std::make_shared<int>(0);

    auto f = [&] () -> int { return ++(*counter); };

    return f;
}


int main()
{
   auto counter1 = create_counter();
   auto counter2 = create_counter();

   std::cout << counter1() << std::endl;
   std::cout << counter1() << std::endl;

   std::cout << counter2() << std::endl;
   std::cout << counter2() << std::endl;

   std::cout << counter1() << std::endl;


   return 0;
}

此代码返回:

1
2
3
4
5

但我期待它回归:

1
2
1
2
3

进一步修改

感谢您将错误指向我的原始代码。我现在看到发生的事情是在调用create_couter之后指针被删除了,而new create只是重用了相同的内存地址。

这让我想到了我真正的问题,我想做的是:

std::function<int()> create_counter()
{
    int counter = 0;

    auto f = [&] () -> int { return ++counter; };

    return f;
}

如果C ++ lambdas是真正的闭包,每个本地计数器将与返回的函数共存(该函数承载其环境 - 至少是其中的一部分)。相反,在调用create_counter之后销毁计数器,并且调用返回的函数会创建分段错误。这不是关闭的预期行为。

Marco A建议解决方法:通过复制使指针通过。这会增加引用计数器,因此在create_counter之后它不会被销毁。但这就是kludge。但是,正如马可指出的那样,它的确有效并且完全符合我的预期。

Jarod42建议声明变量,并将其初始化为捕获列表的一部分。但这违背了闭包的目的,因为变量然后是函数的本地变量,而不是创建函数的环境。

苹果苹果建议使用静态计数器。但这是一种解决方法,可以避免在create_function结束时破坏变量,这意味着所有返回的函数共享同一个变量,而不是它们运行的​​环境。

所以我认为结论(除非有人能说得更多)是C ++中的lambdas不是真正的闭包。

再次感谢您的评论。

5 个答案:

答案 0 :(得分:12)

共享指针在函数范围的末尾被销毁并且内存被释放:你正在存储一个悬空引用

std::function<int()> create_counter()
{
  std::shared_ptr<int> counter = std::make_shared<int>(0);

  auto f = [&]() -> int { return ++(*counter); };

  return f;
} // counter gets destroyed

因此调用undefined behavior。通过用类或结构替换整数来测试它,并检查析构函数是否实际被调用。

通过捕获会增加共享指针的使用计数器并防止出现问题

auto f = [=]() -> int { return ++(*counter); };
          ^

答案 1 :(得分:4)

如上所述,你有悬空引用,因为局部变量在范围的末尾被销毁。

您可以将功能简化为

std::function<int()> create_counter()
{
    int counter = 0;

    return [=] () mutable -> int { return ++counter; };
}

或甚至(在C ++ 14中)

auto create_counter()
{
    return [counter = 0] () mutable -> int { return ++counter; };
}

Demo

答案 2 :(得分:2)

Lambda 表达式 — 一个 lambda 表达式指定一个内联指定的对象,而不仅仅是一个没有名称的函数,能够在范围内捕获变量。

  1. Lambda 经常可以作为对象传递。
  2. 除了自己的函数参数之外,lambda 表达式还可以引用其定义范围内的局部变量。

关闭 -

闭包是可以捕获环境的特殊函数,即词法范围内的变量*.*

<块引用>

闭包是关闭它所在环境的任何函数 被定义。这意味着它可以访问变量,而不是在它的 参数列表。

  • 这里的 C++ 特定部分是什么 闭包是编程中的一个通用概念,起源于函数式编程。当我们谈论 C++ 中的闭包时,它们总是带有 lambda 表达式(有些学者更喜欢在其中包含函数对象)

在 C++ 中,lambda 表达式是用于创建特殊临时对象的语法,该对象的行为类似于函数对象的行为方式。

<块引用>

C++ 标准专门将这种类型的对象称为 闭包对象。这与更广泛的有点不一致 闭包的定义,它指的是任何函数,匿名的或 不是,它从定义它们的环境中捕获变量。

就标准而言,所有 lambda 表达式的实例化都是闭包对象,即使它们的捕获组中没有任何捕获。

https://pranayaggarwal25.medium.com/lambdas-closures-c-d5f16211de9a

答案 3 :(得分:0)

如果你想要1 2 3 4 5,你也可以试试这个

std::function<int()> create_counter()
{
    static int counter = 0;

    auto f = [&] () -> int { return ++counter; };

    return f;
}

答案 4 :(得分:0)

如果变量由vaule捕获,则它是从原始变量构造的副本。如果通过引用,您可以将它们视为对同一对象的不同引用。