【发布时间】:2012-05-20 01:31:36
【问题描述】:
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 失去了它的价值。
因此,我设法在 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 中的安全可返回闭包对于使用它们至关重要,有谁知道如何更优雅地实现这一点?
【问题讨论】:
-
您应该在 Valgrind 中运行这样的测试,因为您在访问释放的内存时会偶然看到正确的行为。