在 C++11 lambda 语法中,堆分配的闭包
In C++11 lambda syntax, heap-allocated closures?
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("%dn", g());
printf("%dn", g());
printf("%dn", g());
printf("%dn", 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("%dn", g());
printf("%dn", g());
printf("%dn", g());
printf("%dn", 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有一个扩展,即使有多个语句也允许返回类型推断,但你至少应该得到一个警告。
这是我的测试代码。它使用递归函数将 lambda 参数的地址与其他基于堆栈的变量进行比较。
#include <stdio.h>
#include <functional>
void fun2( std::function<void()> callback ) {
(callback)();
}
void fun1(int n) {
if(n <= 0) return;
printf("stack address = %p, ", &n);
fun2([n]() {
printf("capture address = %pn", &n);
fun1(n - 1);
});
}
int main() {
fun1(200);
return 0;
}
使用 mingw64 编译代码并在 Win7 上运行,它输出
stack address = 000000000022F1E0, capture address = 00000000002F6D20
stack address = 000000000022F0C0, capture address = 00000000002F6D40
stack address = 000000000022EFA0, capture address = 00000000002F6D60
stack address = 000000000022EE80, capture address = 00000000002F6D80
stack address = 000000000022ED60, capture address = 00000000002F6DA0
stack address = 000000000022EC40, capture address = 00000000002F6DC0
stack address = 000000000022EB20, capture address = 00000000007A7810
stack address = 000000000022EA00, capture address = 00000000007A7820
stack address = 000000000022E8E0, capture address = 00000000007A7830
stack address = 000000000022E7C0, capture address = 00000000007A7840
很明显,捕获的参数不在堆栈区域,并且捕获的参数的地址不连续。
所以我相信一些编译器可能会使用动态内存分配来
捕获 lambda 参数。
- 如何将C++闭包与变量参数同时重用——类似于JavaScript
- C++中的 JavaScript 样式闭包
- 如何使用类模拟 C++11 中的 lambda 函数和闭包?
- 使用带有闭包的 lambda 的回调
- C++中的 Lambda <-> 闭包等价性
- 为什么 std::remove_if 会创建如此多的闭包?
- Lambda闭包左值可以作为右值参考参数传递
- 如何声明接受字符串、返回void的闭包的类型
- 添加了闭包的 Perl newXS()
- 具有意外结果的 C++ 闭包
- 关于函数模板中定义的 lambda 闭包类型可以说些什么?
- 基类如何调用派生类在 c++ 中传递的闭包?
- 通过引用将自定义结构的向量传递给 boost::compute 闭包或函数
- 创建特征以检测C++中的闭包类型
- 在 c++ 中使用函数装饰器(使用闭包)时出现意外的分段错误
- Lambda 表达式闭包函数不起作用
- Lambda 闭包类型构造函数
- 在闭包中,如何通过存储在内存中的指针或引用类型捕获可变性或用现代函数式语言进行处理?
- boost::compute,将指针传递给闭包
- 在 C++11 lambda 语法中,堆分配的闭包