lambda 捕获的变量存储在哪里?
Where are lambda captured variables stored?
这个例子怎么可能行得通?它打印 6
:
#include <iostream>
#include <functional>
using namespace std;
void scopeIt(std::function<int()> &fun) {
int val = 6;
fun = [=](){return val;}; //<-- this
}
int main() {
std::function<int()> fun;
scopeIt(fun);
cout << fun();
return 0;
}
在 scopeIt
调用完成后,值 6
存储在哪里?如果我用 [&]
替换 [=]
,它会打印 0
而不是 6
。
它存储在闭包中,然后在您的代码中存储在 std::function<int()> &fun
。
lambda 生成的内容等同于编译器生成的实例 class。
此代码:
[=](){return val;}
生成与此等效的内容...这将是 "closure":
struct UNNAMED_TYPE
{
UNNAMED_TYPE(int val) : val(val) {}
const int val;
// Above, your [=] "equals/copy" syntax means "find what variables
// are needed by the lambda and copy them into this object"
int operator() () const { return val; }
// Above, here is the code you provided
} (val);
// ^^^ note that this DECLARED type is being INSTANTIATED (constructed) too!!
lambda表达式的值是class类型的对象,并且
For each entity
captured by copy, an unnamed non-static data member is declared in the closure type.
(C++11 中的[expr.prim.lambda]/14)
即lambda创建的对象
[=](){return val;}
实际包含一个int
类型的非静态成员,其值为6,将此对象复制到std::function
对象中。
所谓的闭包类型(也就是lambda表达式的class类型)对于每个捕获的实体都有成员。这些成员是按值捕获的对象,以及按引用捕获的引用。它们使用捕获的实体进行初始化,并独立存在于 闭包对象 (此 lambda 指定的闭包类型的特定对象)中。
与val
的值捕获相对应的未命名成员用val
初始化并从闭包类型operator()
的内部访问,这很好。闭包对象可能很容易被复制或移动多次,直到发生这种情况,这也很好 - 闭包类型隐式定义了移动和复制构造函数,就像正常的 classes 一样。
但是,当通过引用捕获时,在 main
中调用 fun
时隐式执行的左值到右值转换会引发未定义的行为,因为 引用成员引用的对象已经已被销毁 - 即我们正在使用悬空引用。
C++ 中的 Lambda 实际上只是 "anonymous" 结构仿函数。所以当你这样写的时候:
int val = 6;
fun = [=](){return val;};
编译器将其翻译成以下内容:
int val = 6;
struct __anonymous_struct_line_8 {
int val;
__anonymous_struct_line_8(int v) : val(v) {}
int operator() () const {
return val; // returns this->val
}
};
fun = __anonymous_struct_line_8(val);
然后,std::function
通过 type erasure 存储该仿函数。
当您使用 [&]
而不是 [=]
时,它会将结构更改为:
struct __anonymous_struct_line_8 {
int& val; // Notice this is a reference now!
...
所以现在对象存储了对函数 val
对象的引用,在函数退出后它变成了悬空(无效)引用(并且你会得到未定义的行为)。
这个例子怎么可能行得通?它打印 6
:
#include <iostream>
#include <functional>
using namespace std;
void scopeIt(std::function<int()> &fun) {
int val = 6;
fun = [=](){return val;}; //<-- this
}
int main() {
std::function<int()> fun;
scopeIt(fun);
cout << fun();
return 0;
}
在 scopeIt
调用完成后,值 6
存储在哪里?如果我用 [&]
替换 [=]
,它会打印 0
而不是 6
。
它存储在闭包中,然后在您的代码中存储在 std::function<int()> &fun
。
lambda 生成的内容等同于编译器生成的实例 class。
此代码:
[=](){return val;}
生成与此等效的内容...这将是 "closure":
struct UNNAMED_TYPE
{
UNNAMED_TYPE(int val) : val(val) {}
const int val;
// Above, your [=] "equals/copy" syntax means "find what variables
// are needed by the lambda and copy them into this object"
int operator() () const { return val; }
// Above, here is the code you provided
} (val);
// ^^^ note that this DECLARED type is being INSTANTIATED (constructed) too!!
lambda表达式的值是class类型的对象,并且
For each entity captured by copy, an unnamed non-static data member is declared in the closure type.
(C++11 中的[expr.prim.lambda]/14)
即lambda创建的对象
[=](){return val;}
实际包含一个int
类型的非静态成员,其值为6,将此对象复制到std::function
对象中。
所谓的闭包类型(也就是lambda表达式的class类型)对于每个捕获的实体都有成员。这些成员是按值捕获的对象,以及按引用捕获的引用。它们使用捕获的实体进行初始化,并独立存在于 闭包对象 (此 lambda 指定的闭包类型的特定对象)中。
与val
的值捕获相对应的未命名成员用val
初始化并从闭包类型operator()
的内部访问,这很好。闭包对象可能很容易被复制或移动多次,直到发生这种情况,这也很好 - 闭包类型隐式定义了移动和复制构造函数,就像正常的 classes 一样。
但是,当通过引用捕获时,在 main
中调用 fun
时隐式执行的左值到右值转换会引发未定义的行为,因为 引用成员引用的对象已经已被销毁 - 即我们正在使用悬空引用。
C++ 中的 Lambda 实际上只是 "anonymous" 结构仿函数。所以当你这样写的时候:
int val = 6;
fun = [=](){return val;};
编译器将其翻译成以下内容:
int val = 6;
struct __anonymous_struct_line_8 {
int val;
__anonymous_struct_line_8(int v) : val(v) {}
int operator() () const {
return val; // returns this->val
}
};
fun = __anonymous_struct_line_8(val);
然后,std::function
通过 type erasure 存储该仿函数。
当您使用 [&]
而不是 [=]
时,它会将结构更改为:
struct __anonymous_struct_line_8 {
int& val; // Notice this is a reference now!
...
所以现在对象存储了对函数 val
对象的引用,在函数退出后它变成了悬空(无效)引用(并且你会得到未定义的行为)。