std::functions 和 lambda 函数传递

std::functions and lambda function passing

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

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

发生这种情况的原因是什么?

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被丢弃

由于 TestClassconst& 多了 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。

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

TestClass t(__f);

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

在这一行之后,对 .F() 的调用在悬空引用之后结束,并导致未定义的行为。

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

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

你的第三个问题是变量名中的双下层存储。 (或者以 _ 后跟大写字母开头的标识符)。

如果您想知道 std::function 是如何工作的以及它是什么,有很多关于该主题的优秀 SO 帖子,其中包含各种级别的技术细节。

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

对于第一种情况,

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

创建了一个临时的 lambda,然后将其转换为 std::function,这也是一个临时的。临时std::function绑定到构造函数的参数_f,绑定到成员f。这条语句后临时对象将被销毁,然后 f 变为悬挂,当 t.F(); 时失败。

对于第二种情况,

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

创建了一个临时 lambda,然后将其绑定到引用(到 const)。然后把它的生命周期延长到引用的生命周期__f,这样代码就可以了

对于第3种情况,

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++ 中是保留的。