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

Ste*_*eve 21 c++ lambda c++11

C++ 11 lambdas很棒!

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

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

#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());
}
Run Code Online (Sandbox Code Playgroud)

给,

$ g++-4.5 -std=c++0x -o test test.cpp && ./test
121
8365280
8365280
8365280
Run Code Online (Sandbox Code Playgroud)

原因是在f1()返回后,k超出范围但仍在堆栈上.所以第一次g()执行k是好的,但在那之后堆栈被破坏并k失去其价值.

因此,我在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());
}
Run Code Online (Sandbox Code Playgroud)

这里,[=]用于确保共享指针被复制,而不是被引用,以便正确完成内存处理:k当生成的函数g超出范围时,应释放堆分配的副本.结果是所希望的,

$ g++-4.5 -std=c++0x -o test test.cpp && ./test
121
122
123
124
Run Code Online (Sandbox Code Playgroud)

通过解除引用来引用变量是非常难看的,但应该可以使用引用:

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++;});
}
Run Code Online (Sandbox Code Playgroud)

实际上,这奇怪地给了我,

$ g++-4.5 -std=c++0x -o test test.cpp && ./test
0
1
2
3
Run Code Online (Sandbox Code Playgroud)

知道为什么吗?也许采用共享指针的引用是不礼貌的,现在我考虑一下,因为它不是被跟踪的引​​用.我发现将引用移到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++;});
}
Run Code Online (Sandbox Code Playgroud)

给予,

g++-4.5 -std=c++0x -o test test.cpp && ./test
156565552
/bin/bash: line 1: 25219 Segmentation fault      ./test
Run Code Online (Sandbox Code Playgroud)

在任何情况下,如果有一种方法可以通过堆分配自动生成安全可恢复的闭包,那将是很好的.例如,如果有一个替代,[=]并且[&]表明变量应该通过对共享指针的引用进行堆分配和引用.我最初想到的std::function是,它创建了一个封装闭包的对象,因此它可以为闭包环境提供存储,但我的实验表明这似乎没有帮助.

我认为C++ 11中安全可恢复的闭包对于使用它们是至关重要的,有谁知道如何更优雅地完成它?

bam*_*s53 24

f1你得到了你说的原因未定义的行为; lambda包含对局部变量的引用,在函数返回后,引用不再有效.要解决这个问题,您不必在堆上进行分配,只需声明捕获的值是可变的:

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

但是你必须要小心使用这个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++;});
Run Code Online (Sandbox Code Playgroud)

f4再次是未定义的行为,因为你再次捕获对局部变量的引用,o.您应该只是按值捕获,但仍然int &p在lambda中创建内部以获得所需的语法.

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

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