C++ lambda词法闭包局部变量

Chr*_*Vig 4 c++ lambda c++11

摘要

在C++中,当我从一个函数返回一个lambda时,该函数捕获了该函数的局部变量,具体发生了什么,为什么?编译器(g ++)似乎允许它,但它给了我不同于我期望的结果,所以我不确定这是否在技术上是安全/支持的.

细节

在某些语言(Swift,Lisp等)中,你可以在闭包/ lambda中捕获局部变量,只要闭包在范围内,它们就会保持有效并且在范围内(我听说它叫做"lambda over over lambda") "在Lisp语境中).例如,在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
Run Code Online (Sandbox Code Playgroud)

我尝试编写与此类似的C++,如下所示:

auto counter(int initial)
{
    int count = initial;
    return [&count] () -> int {
        count = count + 1;
        return count;
    };
}
Run Code Online (Sandbox Code Playgroud)

但是,我得到的结果是:

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
Run Code Online (Sandbox Code Playgroud)

如果我捕获仍在范围内的变量,它就像我期望的那样工作.例如,如果我在单个函数中执行以下所有操作:

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
Run Code Online (Sandbox Code Playgroud)

所以我想我的问题是,在上面的第一个C++示例中,实际捕获了什么?它是否定义了行为,或者我只是引用堆栈上的一些随机内存?

Sam*_*hik 9

    return [&count] () -> int {
Run Code Online (Sandbox Code Playgroud)

这是一个参考捕获.lambda捕获对此对象的引用.

有问题的对象位于count函数的本地范围内,因此当函数返回时,count会被销毁,这将成为对超出范围并被销毁的对象的引用.使用此引用将变为未定义的行为.

按价值捕获似乎解决了这个问题:

    return [count] () -> int {
Run Code Online (Sandbox Code Playgroud)

但是你明显的意图是每次调用这个lambda都会返回一个单调递增的计数器值.仅仅按值捕获对象是不够的.你还需要使用一个可变的lambda:

 return [count] () mutable -> int
 {
    return ++count;
 };
Run Code Online (Sandbox Code Playgroud)

但对你的问题"发生了什么"的迂腐回答是,lambda本质上是一个匿名类,而lambda捕获的实际上是类成员.你的lambda相当于:

class SomeAnonymousClassName {

     int &count;

public:
     SomeAnonymousClassName(int &count) : count(count)
     {}

     int operator()
     {
          // Whatever you stick in your lambda goes here.
     }
};
Run Code Online (Sandbox Code Playgroud)

通过引用捕获某些内容会转换为引用的类成员.按值捕获某些内容会转换为不是引用的类成员,捕获lambda变量的行为转换为将它们传递给lambda类的构造函数,这就是创建lambda时会发生的情况.lambda实际上是一个匿名类的实例,带有一个定义的operator().

在常规lambda中,operator()实际上是一个const运算符方法.在可变的lambda中,operator()是一个非const可变的运算符方法.