std::functions 和 lambda 函数传递

std::functions and lambda function passing

本文关键字:函数 functions std lambda      更新时间:2023-10-16

我有一个类,它将std::function作为参数,我为其分配了一个lambda函数。它在构造函数中工作,但之后停止工作。调试器在运行第一行后说f为"空"。为什么?

#include <iostream>
#include <string>
#include <functional>
typedef std::function<void(std::string)> const& fn;
class TestClass
{
public:
    TestClass(fn _f) : f(_f) { F(); }
    void F() { f("hello"); };
private:
    fn f;
};

int main()
{
    TestClass t([](std::string str) {std::cout << str << std::endl; });
    t.F();
    return 0;
}

调用t.F()会导致故障。为什么?

我可以通过将其更改为以下内容来解决它:

int main()
{
    fn __f = [](std::string str) {std::cout << str << std::endl; };
    TestClass t(__f);
    t.F();
    return 0;
}

但同样,当我将fn更改为auto时,这不起作用!

int main()
{
    auto __f = [](std::string str) {std::cout << str << std::endl; };
    TestClass t(__f);
    t.F();
    return 0;
}
为什么

会发生这种情况,这是什么解释?

请注意,(1( fn被定义为引用(对 const(;(2(λ和std::function不是同一类型;(3( 不能直接绑定对不同类型的对象的引用。

对于第一种情况,

TestClass t([](std::string str) {std::cout << str << std::endl; });
t.F();

创建一个临时 lambda,然后将其转换为 std::function这也是一个临时的。临时std::function绑定到构造函数的参数_f,并绑定到成员f。临时将在此语句之后被销毁,然后ft.F();失败时变得悬而未决。

对于第二种情况,

fn __f = [](std::string str) {std::cout << str << std::endl; };
TestClass t(__f);
t.F();

创建一个临时 lambda,然后将其绑定到引用(到 const(。然后它的生命周期被延长到引用__f的生命周期,所以代码很好。

对于第三种情况,

auto __f = [](std::string str) {std::cout << str << std::endl; };
TestClass t(__f);
t.F();

创建 lambda 然后转换为临时std::function。临时std::function绑定到构造函数的参数_f,并绑定到成员f。临时将在此语句之后被销毁,然后f变得悬而未决,当t.F();它失败时。


(1(你可以像typedef std::function<void(std::string)> fn;一样将fn声明为非引用,然后std::function将被复制,每个案例都会很好地工作。
(2(不要使用以双下划线开头的名称,它们是C++保留的。

typedef std::function<void(std::string)> const& fn;

这不是std::function,而是对std::function的引用。

TestClass(fn _f) : f(_f) { F(); }
fn f;

在这里,您将const&带到std::function并将其绑定到另一个const& std::function。 构造函数主体中的F()有效,因为引用至少与构造函数一样有效。

TestClass t([](std::string str) {std::cout << str << std::endl; });

这将创建一个从 lambda 创建的临时std::function。 这个临时持续的时间与当前行一样长(直到;(。

然后丢弃临时std::function

由于TestClass通过const&来获取std::function,因此它不会延长临时寿命。

因此,在该行之后,std::function const&的任何调用都是未定义的行为,您可以在稍后.F()调用中看到。

fn __f = [](std::string str) {std::cout << str << std::endl; };

这确实引用了生存期延长。 从 lambda 创建的临时std::function的生存期延长至 __f 变量的生存期。

顺便说一句,此行还通过具有包含双下划线的变量而使您的程序格式不正确,无需诊断。 此类标识符是为编译器的实现保留的,您不能创建它们。

TestClass t(__f);

然后我们传递这个引用(指终身延长的临时(,一切正常。

auto __f = [](std::string str) {std::cout << str << std::endl; };

这将创建一个变量__f(见上文,错误名称(,它是一个 lambda。

λ不是std::function。 可以从 lambda 隐式创建std::function

TestClass t(__f);

这会从 lambda 创建一个临时std::function,将其传递给 TestClass 构造函数,然后销毁临时构造函数。

在此行之后,对.F()的调用最终遵循悬而未决的引用,并产生未定义的行为。

的核心问题可能是你认为 lambda 是一个std::function . 不是。 std::function可以存储 lambda。

你的第二个问题是将某些东西类型定义为const&,这几乎总是一个非常愚蠢的想法。 引用的行为在基本方面与值不同。

第三个问题是变量名称中的双底存储。 (或以_开头,后跟大写字母的标识符(。

如果您想知道std::function是如何工作的以及它是什么,有很多关于该主题的好 SO 帖子,具有不同级别的技术细节。