了解 C++ lambda 函数中的引用捕获
Understanding capture by reference in C++ lamdba functions
在我遇到这种情况之前,我以为我了解 C++ 中引用捕获的工作原理:
auto inrcrementer = []() {
int counter = 0;
return [&counter]() {
return counter++;
};
};
int main() {
auto inc = inrcrementer();
cout << inc() << ", " << inc() << ", " << inc() << ", " << endl;
return 0;
}
我原以为 return 0, 1, 2
但结果 return 0, 32765, 32765
。为什么?
此外,如果我将其更改为:
auto inrcrementer = []() {
int counter = 0;
return [counter]() mutable {
return counter++;
};
};
int main() {
auto inc = inrcrementer();
cout << inc() << ", " << inc() << ", " << inc() << ", " << endl;
return 0;
}
已修复,并且 return 符合预期。两者有什么区别?
问题在于,在您的第一个示例中,lambda 存储了对 counter
的引用,而不是其副本。但是当 incrementer
returns 时 counter
超出范围,因此引用悬而未决。然后,当您调用 incrementer
返回的 lambda 时,该引用无效。
这在实践中通常意味着它指向堆栈中已经/正在用于其他用途的区域。这就是为什么你会得到奇怪的结果。
在您的第二个示例中,lambda 有自己的 counter
副本,并且只要 lambda 仍然存在,它就一直在范围内。因为你已经声明了 lambda mutable
,它可以用它的 counter
的(私有)副本做任何它喜欢的事情,而不会被其他人弄乱。
因此通过引用捕获可能很危险。小心使用!引用实际上只是一个指针,在表面之下,但因为它看起来不像一个指针,所以很容易滑倒。
展开外层lambda后,你的第一个例子可以这样写:
struct incrementer
{
auto operator()()
{
int counter = 0;
return [&counter]() { return counter++;}
}
};
随着它现在变得更加明显,counter
是一个局部变量,内部 lambda 操作对局部对象的引用,一个在调用站点的悬空对象。
第二个例子可以展开成这样:
struct incrementer
{
auto operator()()
{
int counter = 0;
struct inner
{
inner(int cnt) : mcnt{cnt}();
int operator()() { return mcnt++; }
int mcnt;
};
return inner{counter};
}
};
基本上,计数器的生命周期需要在 lambda 的整个生命周期内以某种方式保留。
另一种方法是将计数器存储在外层 lambda 中,例如
auto inrcrementer = [counter=0]() mutable {
return [&counter]() {
return counter++;
};
};
只要内层的寿命不超过外层就可以了。
在我遇到这种情况之前,我以为我了解 C++ 中引用捕获的工作原理:
auto inrcrementer = []() {
int counter = 0;
return [&counter]() {
return counter++;
};
};
int main() {
auto inc = inrcrementer();
cout << inc() << ", " << inc() << ", " << inc() << ", " << endl;
return 0;
}
我原以为 return 0, 1, 2
但结果 return 0, 32765, 32765
。为什么?
此外,如果我将其更改为:
auto inrcrementer = []() {
int counter = 0;
return [counter]() mutable {
return counter++;
};
};
int main() {
auto inc = inrcrementer();
cout << inc() << ", " << inc() << ", " << inc() << ", " << endl;
return 0;
}
已修复,并且 return 符合预期。两者有什么区别?
问题在于,在您的第一个示例中,lambda 存储了对 counter
的引用,而不是其副本。但是当 incrementer
returns 时 counter
超出范围,因此引用悬而未决。然后,当您调用 incrementer
返回的 lambda 时,该引用无效。
这在实践中通常意味着它指向堆栈中已经/正在用于其他用途的区域。这就是为什么你会得到奇怪的结果。
在您的第二个示例中,lambda 有自己的 counter
副本,并且只要 lambda 仍然存在,它就一直在范围内。因为你已经声明了 lambda mutable
,它可以用它的 counter
的(私有)副本做任何它喜欢的事情,而不会被其他人弄乱。
因此通过引用捕获可能很危险。小心使用!引用实际上只是一个指针,在表面之下,但因为它看起来不像一个指针,所以很容易滑倒。
展开外层lambda后,你的第一个例子可以这样写:
struct incrementer
{
auto operator()()
{
int counter = 0;
return [&counter]() { return counter++;}
}
};
随着它现在变得更加明显,counter
是一个局部变量,内部 lambda 操作对局部对象的引用,一个在调用站点的悬空对象。
第二个例子可以展开成这样:
struct incrementer
{
auto operator()()
{
int counter = 0;
struct inner
{
inner(int cnt) : mcnt{cnt}();
int operator()() { return mcnt++; }
int mcnt;
};
return inner{counter};
}
};
基本上,计数器的生命周期需要在 lambda 的整个生命周期内以某种方式保留。
另一种方法是将计数器存储在外层 lambda 中,例如
auto inrcrementer = [counter=0]() mutable {
return [&counter]() {
return counter++;
};
};
只要内层的寿命不超过外层就可以了。