gpt4 book ai didi

c++ - 在 C++11 lambda 语法中,堆分配的闭包?

转载 作者:IT老高 更新时间:2023-10-28 21:47:06 30 4
gpt4 key购买 nike

C++11 lambda 很棒!

但是缺少一件事,那就是如何安全地处理可变数据。

以下将在第一次计数后给出错误计数:

#include <cstdio>
#include <functional>
#include <memory>

std::function<int(void)> f1()
{
int k = 121;
return std::function<int(void)>([&]{return k++;});
}

int main()
{
int j = 50;
auto g = f1();
printf("%d\n", g());
printf("%d\n", g());
printf("%d\n", g());
printf("%d\n", g());
}

给予,

$ g++-4.5 -std=c++0x -o test test.cpp && ./test
121
8365280
8365280
8365280

原因是f1()之后返回, k超出范围但仍在堆栈中。所以第一次g()被执行 k没问题,但之后堆栈已损坏,k失去它的值(value)。

因此,我设法在 C++11 中实现安全可返回闭包的唯一方法是在堆上显式分配已关闭变量:

std::function<int(void)> f2()
{
int k = 121;
std::shared_ptr<int> o = std::shared_ptr<int>(new int(k));
return std::function<int(void)>([=]{return (*o)++;});
}

int main()
{
int j = 50;
auto g = f2();
printf("%d\n", g());
printf("%d\n", g());
printf("%d\n", g());
printf("%d\n", g());
}

这里,[=]用于确保共享指针被复制,而不是被引用,以便正确完成内存处理:k 的堆分配拷贝生成函数时应该释放g超出范围。结果如你所愿,

$ g++-4.5 -std=c++0x -o test test.cpp && ./test
121
122
123
124

通过取消引用来引用变量非常难看,但应该可以使用引用来代替:

std::function<int(void)> f3()
{
int k = 121;
std::shared_ptr<int> o = std::shared_ptr<int>(new int(k));
int &p = *o;
return std::function<int(void)>([&]{return p++;});
}

其实,这很奇怪,

$ g++-4.5 -std=c++0x -o test test.cpp && ./test
0
1
2
3

知道为什么吗?考虑到共享指针的引用可能是不礼貌的,因为它不是跟踪引用。我发现将引用移动到 lambda 内部会导致崩溃,

std::function<int(void)> f4()
{
int k = 121;
std::shared_ptr<int> o = std::shared_ptr<int>(new int(k));
return std::function<int(void)>([&]{int &p = *o; return p++;});
}

给予,

g++-4.5 -std=c++0x -o test test.cpp && ./test
156565552
/bin/bash: line 1: 25219 Segmentation fault ./test

无论如何,如果有一种方法可以通过堆分配自动创建可安全返回的闭包,那就太好了。例如,如果有 [=] 的替代品和 [&]这表明变量应该通过对共享指针的引用进行堆分配和引用。了解 std::function 时的初步想法是它创建了一个封装闭包的对象,因此它可以为闭包环境提供存储,但我的实验表明这似乎没有帮助。

我认为 C++11 中的安全可返回闭包对于使用它们至关重要,有谁知道如何更优雅地实现这一点?

最佳答案

f1 中,由于您所说的原因,您会遇到未定义的行为; lambda 包含对局部变量的引用,并且在函数返回后该引用不再有效。为了解决这个问题,您不必在堆上分配,您只需声明捕获的值是可变的:

int k = 121;
return std::function<int(void)>([=]() mutable {return k++;});

您必须小心使用此 lambda,因为它的不同拷贝将修改它们自己的捕获变量的拷贝。通常算法期望使用仿函数的拷贝等同于使用原始函数。我认为只有一种算法实际上允许有状态的函数对象,std::for_each,它返回它使用的函数对象的另一个拷贝,以便您可以访问发生的任何修改。


f3 中,没有任何东西维护共享指针的拷贝,因此正在释放内存并访问它会产生未定义的行为。您可以通过按值显式捕获共享指针并仍按引用捕获指向的 int 来解决此问题。

std::shared_ptr<int> o = std::shared_ptr<int>(new int(k));
int &p = *o;
return std::function<int(void)>([&p,o]{return p++;});

f4 再次是未定义的行为,因为您再次捕获对局部变量 o 的引用。您应该简单地按值捕获,然后仍然在 lambda 中创建您的 int &p 以获得您想要的语法。

std::shared_ptr<int> o = std::shared_ptr<int>(new int(k));
return std::function<int(void)>([o]() -> int {int &p = *o; return p++;});

请注意,当您添加第二条语句时,C++11 不再允许您省略返回类型。 (clang 和我假设 gcc 有一个扩展,即使有多个语句也允许返回类型推导,但你至少应该得到一个警告。)

关于c++ - 在 C++11 lambda 语法中,堆分配的闭包?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10670114/

30 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com