通过引用传递的 Lambda 在构造函数中调用时运行,但稍后存储在数据成员中时不会运行

Roc*_*net 43 c++ lambda lifetime c++11

以下 C++ 代码打印11.1然后崩溃。lambda 函数似乎在构造函数内被正确调用,但后来,相同的函数不再起作用!为什么会发生这种情况?lambda 的寿命有限制吗?

#include <functional>
#include <iostream>

class LambdaStore
{
public:
    LambdaStore(const std::function<void(float)>& _fn)
    : fn(_fn)
    {
        fn(11.1f);    // works
    }

    void ExecuteStoredLambda()
    {
        fn(99.9f);    // crashes
    }

private:
    const std::function<void(float)>& fn;
};

int main()
{
    LambdaStore lambdaStore([](float a) { std::cout << a << '\n'; });

    lambdaStore.ExecuteStoredLambda();
}
Run Code Online (Sandbox Code Playgroud)

Gui*_*cot 52

您不是存储 lambda,而是存储对std::function.

事实上,std::function当 lambda 隐式转换为std::function. 该std::function临时对象在调用构造函数的行之后终止。

    LambdaStore(const std::function<void(float)>& _fn) // _fn refers to a temporary
    : fn(_fn)
    {
        fn(11.1f);    // works
    } // fn (and _fn) dies
Run Code Online (Sandbox Code Playgroud)

但是,即使您将类更改为通过模板直接使用 lambda 类型,lambda 本身也会消失,但对于任何 C++ 类型来说都是如此,无论是什么类型。考虑ints:

class LambdaStore
{
public:
    LambdaStore(const int& _i)
    : i(_i)
    {
        std::cout << i;    // works
    }

    void ExecuteStoredLambda()
    {
        std::cout << i;    // crashes
    }

private:
    const int& i;
};

void main()
{
    LambdaStore lambdaStore(1); // temporary int created here
    // temporary int gone

    lambdaStore.ExecuteStoredLambda();
}
Run Code Online (Sandbox Code Playgroud)

当临时变量绑定到函数参数时,它们的生命周期不会超过为其创建的语句。

但是,如果它直接绑定到成员引用,则它们确实会获得生命周期延长,但仅使用大括号时:

struct ref {
    const int& i
};

int main() {
  ref a{3};

  std::cout << a.i; // works

  ref b(3);

  std::cout << b.i; // crashes
}
Run Code Online (Sandbox Code Playgroud)

解决方案显然是按值存储std::function而不是按引用存储:

class LambdaStore
{
public:
    LambdaStore(const std::function<void(float)>& _fn)
    : fn(_fn)
    {
        fn(11.1f);    // works
    }

    void ExecuteStoredLambda()
    {
        fn(99.9f);    // will also work
    }

private:
    std::function<void(float)> fn; // no & here
};
Run Code Online (Sandbox Code Playgroud)

  • @paxdiablo 我希望这些用户可以访问我的个人资料并进行自我教育。一般来说,我并不认为自己是不可原谅的,但那次事件是不可原谅的。我的意思是,我和莫妮卡的联系完全为零,我发现宗教很可笑,而且我最终仍然支持犹太讨论组的模式。想到SE的行为我还是很生气。无论莫妮卡是否在身边:SE Inc. 都可以简单地公开表示“如果您想回来,我们随时欢迎您回来”。连认罪都不愿意。但不是。 (20认同)
  • @Peter,关于你的莫妮卡绰号,我认为可以肯定地说莫妮卡已经“离开了大楼”。最终将会有整整一代的 SO 用户根本不知道她是谁:-) (3认同)
  • @Peter-ReinstateMonica 绑定到函数参数时,临时的生命周期永远不会延长。这一点从未改变。临时变量将一直存在到语句末尾,即直到`;`。当使用 `()` 构建聚合时,编译器会合成一个构造函数,使其与 C++20 之前具有构造函数的类型更兼容,从而保留行为。`thing(prvalue)` 的语法*永远不会*延长生命周期。而 `type{prvalue}` 总是如此。 (2认同)

JaM*_*MiT 9

拉姆达函数

这可能是你的理解误入歧途的地方。Lambda 是具有成员函数的对象;它们本身并不是函数。operator()它们的定义看起来像函数体,但这实际上是对象的调用运算符 的定义。

您对场景的评估的半修正版本:

lambda对象似乎在构造函数内正确调用了其运算符,但后来,同一个对象不再起作用!

为什么只是“半”修正?因为在内部LambdaStore,您不能直接访问 lambda 对象。相反,您可以通过对象(引用)来访问它std::function。更正确的版本:

std::function对象似乎在构造函数内正确调用了其运算符,但后来,同一个对象不再工作!

如果我去掉“lambda”的概念,也许这会更清楚?您的主要功能基本上是以下功能的语法快捷方式。

struct Functor {
    void operator()(float a) {
        cout << a << endl;
    }
};

int main()
{
    LambdaStore lambdaStore(Functor{});

    lambdaStore.ExecuteStoredLambda();
}
Run Code Online (Sandbox Code Playgroud)

在此版本中,应该更容易看到您创建了一个临时对象作为LambdaStore构造函数的参数。(实际上,您创建了两个临时对象 - 显式Functor对象和隐式std::function<void(float)>对象。)然后您可能会注意到,您存储的引用在构造函数完成后立即变为悬空......

lambda 的寿命有限制吗?

是的。这个 lambda 是一个临时对象(并且不受生命周期延长的影响),因此它的生命周期非常有限。


Jes*_*uhl 6

是的,临时变量(包括 lambda)的生命周期是有限的,并且引用并不能使其保持活动状态。您可能想存储一个副本。int您的问题与您存储引用的任何其他临时变量(例如 a )相同。如果引用的变量要在引用的生命周期内有效,则它必须比引用的生命周期长。

  • Jesper,我更喜欢“在编译时捕获”:-) (6认同)

Phi*_*gan 5

在构造函数中,您引用了一个函数;这就是正在存储的内容。ExecuteStoredLambda()由于传递给构造函数的函数是内联函数,因此在调用时对它的引用不再有效。要使其工作,请传入非内联函数,或者更好的方法是将fn成员更改为对象实例而不是引用。即 const std::function<void(float)> fn;(没有&)