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

问题描述 投票:0回答:4

以下 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();
}
c++ c++11 lambda lifetime
4个回答
36
投票

您没有存储 lambda,而是存储对

std::function
的引用。

事实上,当 lambda 隐式转换为

std::function
时,
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

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

int
s:

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();
}

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

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

struct ref {
    const int& i
};

int main() {
  ref a{3};

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

  ref b(3);

  std::cout << b.i; // crashes
}

解决方案显然是通过值而不是引用来存储

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
};

7
投票

lambda 函数

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

operator()
的定义。

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

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

为什么只是“半”修正?因为在

LambdaStore
内部,您不能直接访问 lambda 对象。相反,您可以通过(引用)
std::function
对象来访问它。更正确的版本:

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

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

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

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

    lambdaStore.ExecuteStoredLambda();
}

在此版本中,应该更容易看到您创建了一个临时对象作为

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

lambda 的寿命有限制吗?

是的,所有具有非静态(和非线程)存储持续时间的对象在 C++ 中都有有限的生命周期。


6
投票

是的,临时变量(包括 lambda)的生命周期是有限的,引用并不能使其保持活动状态。您可能想存储一个副本。您的问题与您存储引用的任何其他临时变量(如

int
)相同。如果引用的变量要在引用的生命周期内有效,则它必须比引用的生命周期长。


5
投票

在构造函数中,您引用了一个函数;这就是正在存储的内容。由于传递给构造函数的函数是内联函数,因此在调用

ExecuteStoredLambda()
时,对其的引用不再有效。要使其正常工作,请传入非内联函数,或者更好的是,将
fn
成员更改为对象实例而不是引用。 IE
const std::function<void(float)> fn;
(无&)

© www.soinside.com 2019 - 2024. All rights reserved.