我有一个类,它将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;
}
为什么会发生这种情况有什么解释?
答案 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
。
由于TestClass
取std::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帖子,有各种级别的技术细节。