【问题标题】:C++ lambda lexical closure over local variables局部变量上的 C++ lambda 词法闭包
【发布时间】:2016-12-26 05:46:30
【问题描述】:

总结

在 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++ 示例中,实际捕获了什么?它是定义的行为,还是我只是引用了堆栈上的一些随机内存?

【问题讨论】:

  • 我不明白这行auto c = counter();。如何在没有参数的情况下调用函数?似乎这甚至无法编译。
  • @Richard,已修复,对此感到抱歉。
  • 在第一种情况下,您只是引用了堆栈上的一些随机内存。

标签: c++ c++11 lambda


【解决方案1】:
    return [&count] () -> int {

这是一个引用捕获。 lambda 捕获对此对象的引用。

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

按值捕获似乎可以解决这个问题:

    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,一种可变运算符方法。

【讨论】:

  • 对不起,我不知道为什么我的编辑错误地删除了你的最后一段
  • 啊酷。感谢您的精彩解释和指向可变 lambda 的指针。
【解决方案2】:

在第一种情况下,您正在捕获对局部变量的引用。函数返回后,引用变为悬空引用。因此,您的程序会受到未定义行为的影响。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-21
    • 2018-12-24
    • 1970-01-01
    • 1970-01-01
    • 2017-04-30
    • 2016-09-02
    相关资源
    最近更新 更多