在下面的代码中,我创建了一个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不是真正的闭包。
再次感谢您的评论。
答案 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; };
}
答案 2 :(得分:2)
Lambda 表达式 — 一个 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捕获,则它是从原始变量构造的副本。如果通过引用,您可以将它们视为对同一对象的不同引用。