我通过引用两个lambda来传递我的局部变量。我在函数范围之外调用这些lambdas。这是undefined
吗?
std::pair<std::function<int()>, std::function<int()>> addSome() {
int a = 0, b = 0;
return std::make_pair([&a,&b] {
++a; ++b;
return a+b;
}, [&a, &b] {
return a;
});
}
int main() {
auto f = addSome();
std::cout << f.first() << " " << f.second();
return 0;
}
但是,如果不是,则一个lambda中的更改不会反映在其他lambda中。
我是否误解了lambdas背景下的传递参考?
我正在写变量,它似乎工作正常,输出没有运行时错误
2 0
。如果它工作,那么我期望输出2 1
。
答案 0 :(得分:23)
是的,这会导致未定义的行为。 lambdas将引用超出范围的堆栈分配对象。 (从技术上讲,据我所知,行为是在lambda访问a
和/或b
之前定义的。如果你从不调用返回的lambdas那么就没有UB。)
这是未定义的行为,就像它的未定义行为返回对堆栈分配的本地的引用一样,然后在本地超出范围后使用该引用,除了在这种情况下它&#39;有点被lambda混淆了。
此外,请注意调用lambdas的顺序是未指定的 - 编译器可以在f.second()
之前自由调用f.first()
,因为它们都是同一个完整表达式的一部分。因此,即使我们修复了因使用对已销毁对象的引用而导致的未定义行为,2 0
和2 1
仍然仍然来自此程序的有效输出,并且您得到的取决于编译器决定执行lambdas的顺序。请注意,这是未定义的行为,因为编译器根本无法执行任何事情,而只是在决定顺序时有一定的自由度做一些事情。
(请记住,<<
函数中的main()
正在调用自定义operator<<
函数,并且未指定评估函数参数的顺序。编译器可以自由发出代码它以任何顺序计算同一个完整表达式中的所有函数参数,其约束条件是在调用该函数之前必须计算函数的所有参数。)
要解决第一个问题,请使用std::shared_ptr
创建引用计数对象。按值捕获此共享指针,只要它们(及其任何副本)存在,lambdas将使指向的对象保持活动状态。这个堆分配的对象是我们存储a
和b
共享状态的地方。
要解决第二个问题,请在单独的语句中评估每个lambda。
以下是修复了未定义行为的代码,并保证在f.first()
之前调用f.second()
:
std::pair<std::function<int()>, std::function<int()>> addSome() {
// We store the "a" and "b" ints instead in a shared_ptr containing a pair.
auto numbers = std::make_shared<std::pair<int, int>>(0, 0);
// a becomes numbers->first
// b becomes numbers->second
// And we capture the shared_ptr by value.
return std::make_pair(
[numbers] {
++numbers->first;
++numbers->second;
return numbers->first + numbers->second;
},
[numbers] {
return numbers->first;
}
);
}
int main() {
auto f = addSome();
// We break apart the output into two statements to guarantee that f.first()
// is evaluated prior to f.second().
std::cout << f.first();
std::cout << " " << f.second();
return 0;
}
(See it run。)
答案 1 :(得分:10)
不幸的是,C ++ lambdas可以通过引用捕获但不能解决&#34; upwards funarg problem&#34;。
这样做需要在&#34; cell&#34;中分配捕获的本地和垃圾收集或引用计数用于解除分配。 C ++没有这样做,不幸的是,这使得C ++ lambdas比其他语言(如Lisp,Python或Javascript)更有用,更危险。
更具体地说,根据我的经验,您应该不惜一切代价避免通过引用隐式捕获(即使用[&](…){…}
形式)来保存在本地范围内的lambda对象,因为这是随后段维护的后续配方
始终仔细规划捕获的内容以及捕获的参考文献的生命周期。
当然,使用[&]
引用捕获所有内容是安全的,如果你所做的只是使用同一范围内的lambda 来将代码传递给算法像std::sort
一样,无需在函数之外定义命名的比较器函数。
有时可行的方法是将值 a shared_ptr
捕获到堆分配状态。这基本上是手工实现Python自动执行的操作(但要注意参考周期以避免内存泄漏:Python有一个垃圾收集器,C ++没有)。
答案 2 :(得分:0)
当你超出范围时。这将制作当地人的副本。
void func(void)
{
auto mylocal = getFromSomeWhere();
doSearch([=] {
mylocal->foudnSomthing();
});
}
当您在范围内时,最好通过参考
使用MyType func(void)
{
auto mylocal = getFromSomeWhere();
return ([&] {
return mylocal->doOperation();
})
}