总结
在 C++ 中,当我从捕获该函数局部变量的函数返回 lambda 时,具体会发生什么,为什么?编译器 (g++) 似乎允许这样做,但它给我的结果与我预期的不同,所以我不确定这在技术上是否安全/受支持。
详情
在某些语言(Swift、Lisp 等)中,您可以在闭包/lambda 中捕获局部变量,只要闭包在范围内,它们就保持有效并在范围内(我听说它称为“lambda over在 Lisp 上下文中放弃 lambda”)。例如,在 Swift 中,我尝试做的示例代码是:
func counter(initial: Int) -> (() -> Int) {
var count = initial
return { count += 1; return count }
}
let c = counter(initial: 0)
c() // returns 1
c() // returns 2
c() // returns 3
我尝试编写一个 C++ 等效项,如下所示:
auto counter(int initial)
{
int count = initial;
return [&count] () -> int {
count = count + 1;
return count;
};
}
然而,我得到的结果是:
auto c = counter(0);
std::cout << c() << std::endl; // prints 1
std::cout << c() << std::endl; // prints 1
std::cout << c() << std::endl; // prints 1
如果我捕获了一个仍在范围内的变量,它会按我预期的那样工作。例如,如果我在一个函数中执行以下所有操作:
int count = 0;
auto c = [&count] () -> int {
count = count + 1;
return count;
};
std::cout << c() << std::endl; // prints 1
std::cout << c() << std::endl; // prints 2
std::cout << c() << std::endl; // prints 3
所以我想我的问题是,在上面的第一个 C++ 示例中,实际上捕获了什么?它是定义的行为,还是我只是引用了堆栈上的一些随机内存?
最佳答案
return [&count] () -> int {
这是引用捕获。 lambda 捕获对此对象的引用。
有问题的对象 count
在函数的本地范围内,因此当函数返回时,count
被销毁,this 成为对对象的引用超出范围并被销毁。使用此引用成为未定义的行为。
按值捕获似乎可以解决这个问题:
return [count] () -> int {
但您的明显意图是让此 lambda 的每次调用都返回一个单调递增的计数器值。仅按值捕获对象是不够的。您还需要使用 mutable lambda :
return [count] () mutable -> int
{
return ++count;
};
但是对你的问题“发生了什么”的迂腐回答是,lambda 本质上是一个匿名类,而 lambda 捕获的是真正的类成员。你的 lambda 相当于:
class SomeAnonymousClassName {
int &count;
public:
SomeAnonymousClassName(int &count) : count(count)
{}
int operator()
{
// Whatever you stick in your lambda goes here.
}
};
通过引用捕获某些内容会转换为作为引用的类成员。按值捕获某些东西转化为不是引用的类成员,捕获 lambda 变量的行为转化为将它们传递给 lambda 类的构造函数,这就是创建 lambda 时发生的情况。 lambda 实际上是匿名类的实例,具有已定义的 operator()
。
在常规的 lambda 中,operator()
实际上是一个 const
运算符方法。在可变的 lambda 中,operator()
是一个非 const
,一个可变的运算符方法。
关于局部变量的 C++ lambda 词法闭包,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41326907/