std :: functions和lambda函数传递

时间:2016-08-08 14:18:53

标签: c++ c++11 lambda std-function

我有一个类,它将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;
}

为什么会发生这种情况有什么解释?

2 个答案:

答案 0 :(得分:5)

注意(1)fn被定义为引用(对于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的生命周期,所以代码很好。

对于第三种情况,

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)您可以将fn声明为typedef std::function<void(std::string)> fn;之类的非参考,然后std::function将被复制,并且每个案例都能正常运作。
(2)不要使用以双下划线开头的名称,它们在C ++中保留。 功能

答案 1 :(得分:3)

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

由于TestClassstd::function const&,所以它不会延长临时工的生命周期。

因此,在该行之后,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::function。可以隐式地从lambda创建std::function

TestClass t(__f);

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

在此行之后,对.F()的调用最终会在悬空引用之后发生,并且会产生未定义的行为。

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

你的第二个问题是将const&命名为“{1}},这几乎总是一个非常愚蠢的想法。参考文献的行为与基本方面的价值观不同。

您的第三个问题是变量名称中的双重修正。 (或以_后跟大写字母开头的标识符。)

如果你想知道std::function如何运作以及它是什么,那么在这个主题上有很多好的SO帖子,有各种级别的技术细节。