Lambda 和通过引用捕获局部变量:在作用域之后访问
Lambdas and capture by reference local variables : Accessing after the scope
我通过引用两个 lambda 来传递我的局部变量。我在函数范围之外调用这些 lambda。这是 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 中。
我是否误解了 lambda 上下文中的传递引用?
我正在写入变量,它似乎工作正常,没有输出运行时错误
2 0
。如果它有效,那么我希望输出 2 1
.
是的,这会导致未定义的行为。 lambda 将引用超出范围的堆栈分配对象。 (从技术上讲,据我所知,行为是在 lambda 访问 a
and/or b
之前定义的。如果您从不调用 returned lambda,那么就没有 UB。 )
这是未定义的行为,与 return 对堆栈分配的本地引用的未定义行为相同,然后在本地超出范围后使用该引用,除了在这种情况下它被被 lambda 混淆了一点。
此外,请注意调用 lambda 的顺序是未指定的——编译器可以在 f.first()
之前自由调用 f.second()
,因为两者都是同一个完整表达式的一部分。因此,即使我们修复了因使用对已销毁对象的引用而导致的未定义行为,2 0
和 2 1
都是此程序的 仍然 有效输出,并且您get 取决于编译器决定执行 lambda 的顺序。请注意,这 不是未定义的行为 ,因为编译器根本不能做 任何事情,而只是在决定 做事的顺序.
(请记住,您的 main()
函数中的 <<
正在调用自定义 operator<<
函数,并且未指定计算函数参数的顺序。编译器可以自由地发出以任何顺序计算同一个完整表达式中的所有函数参数的代码,但必须在调用该函数之前计算函数的所有参数的约束。)
要解决第一个问题,请使用 std::shared_ptr
创建引用计数对象。按值捕获此共享指针,只要它们(及其任何副本)存在,lambda 就会使指向的对象保持活动状态。这个堆分配的对象是我们将存储 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.)
不幸的是,C++ lambda 可以通过引用捕获但不能解决“upwards funarg problem”。
这样做需要在“单元格”中分配捕获的局部变量和垃圾收集或用于释放的引用计数。 C++ 没有这样做,不幸的是,这使得 C++ lambda 比其他语言(如 Lisp、Python 或 Javascript.
更不实用且更危险
更具体地说,根据我的经验,您应该不惜一切代价避免通过引用隐式捕获(即使用 [&](…){…}
形式)对于在本地范围内存活的 lambda 对象,因为这是稍后在维护期间出现随机段错误的方法。
始终仔细规划要捕获的内容以及捕获引用的方式和生命周期。
当然,通过 [&]
引用捕获所有内容是安全的,如果您所做的只是在同一范围内使用 lambda 将代码传递给例如std::sort
之类的算法,而无需在函数外部定义命名比较器函数或作为本地使用的实用程序函数(我发现这种用法非常易读且很好,因为您可以隐式地获得很多上下文并且不需要 1 . 为永远不会在其他任何地方重复使用的东西创建一个全局名称,2. 传递大量上下文或仅为该上下文创建额外的 类)。
一种有时可行的方法是通过 value a shared_ptr
捕获到堆分配状态。这基本上是手动实现 Python 自动执行的操作(但要注意引用循环以避免内存泄漏:Python 有垃圾收集器,C++ 没有)。
当您超出范围时。这将复制您的本地人。
void func(void)
{
auto mylocal = getFromSomeWhere();
doSearch([=] {
mylocal->foudnSomthing();
});
}
当你在范围内时,最好通过引用使用
MyType func(void)
{
auto mylocal = getFromSomeWhere();
return ([&] {
return mylocal->doOperation();
})
}
我通过引用两个 lambda 来传递我的局部变量。我在函数范围之外调用这些 lambda。这是 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 中。
我是否误解了 lambda 上下文中的传递引用?
我正在写入变量,它似乎工作正常,没有输出运行时错误
2 0
。如果它有效,那么我希望输出 2 1
.
是的,这会导致未定义的行为。 lambda 将引用超出范围的堆栈分配对象。 (从技术上讲,据我所知,行为是在 lambda 访问 a
and/or b
之前定义的。如果您从不调用 returned lambda,那么就没有 UB。 )
这是未定义的行为,与 return 对堆栈分配的本地引用的未定义行为相同,然后在本地超出范围后使用该引用,除了在这种情况下它被被 lambda 混淆了一点。
此外,请注意调用 lambda 的顺序是未指定的——编译器可以在 f.first()
之前自由调用 f.second()
,因为两者都是同一个完整表达式的一部分。因此,即使我们修复了因使用对已销毁对象的引用而导致的未定义行为,2 0
和 2 1
都是此程序的 仍然 有效输出,并且您get 取决于编译器决定执行 lambda 的顺序。请注意,这 不是未定义的行为 ,因为编译器根本不能做 任何事情,而只是在决定 做事的顺序.
(请记住,您的 main()
函数中的 <<
正在调用自定义 operator<<
函数,并且未指定计算函数参数的顺序。编译器可以自由地发出以任何顺序计算同一个完整表达式中的所有函数参数的代码,但必须在调用该函数之前计算函数的所有参数的约束。)
要解决第一个问题,请使用 std::shared_ptr
创建引用计数对象。按值捕获此共享指针,只要它们(及其任何副本)存在,lambda 就会使指向的对象保持活动状态。这个堆分配的对象是我们将存储 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.)
不幸的是,C++ lambda 可以通过引用捕获但不能解决“upwards funarg problem”。
这样做需要在“单元格”中分配捕获的局部变量和垃圾收集或用于释放的引用计数。 C++ 没有这样做,不幸的是,这使得 C++ lambda 比其他语言(如 Lisp、Python 或 Javascript.
更不实用且更危险更具体地说,根据我的经验,您应该不惜一切代价避免通过引用隐式捕获(即使用 [&](…){…}
形式)对于在本地范围内存活的 lambda 对象,因为这是稍后在维护期间出现随机段错误的方法。
始终仔细规划要捕获的内容以及捕获引用的方式和生命周期。
当然,通过 [&]
引用捕获所有内容是安全的,如果您所做的只是在同一范围内使用 lambda 将代码传递给例如std::sort
之类的算法,而无需在函数外部定义命名比较器函数或作为本地使用的实用程序函数(我发现这种用法非常易读且很好,因为您可以隐式地获得很多上下文并且不需要 1 . 为永远不会在其他任何地方重复使用的东西创建一个全局名称,2. 传递大量上下文或仅为该上下文创建额外的 类)。
一种有时可行的方法是通过 value a shared_ptr
捕获到堆分配状态。这基本上是手动实现 Python 自动执行的操作(但要注意引用循环以避免内存泄漏:Python 有垃圾收集器,C++ 没有)。
当您超出范围时。这将复制您的本地人。
void func(void)
{
auto mylocal = getFromSomeWhere();
doSearch([=] {
mylocal->foudnSomthing();
});
}
当你在范围内时,最好通过引用使用
MyType func(void)
{
auto mylocal = getFromSomeWhere();
return ([&] {
return mylocal->doOperation();
})
}