在我的C ++应用程序中(使用Visual Studio 2010),我需要存储一个std :: function,如下所示:
class MyClass
{
public:
typedef std::function<int(int)> MyFunction;
MyClass (Myfunction &myFunction);
private:
MyFunction m_myFunction; // Should I use this one?
MyFunction &m_myFunction; // Or should I use this one?
};
如您所见,我在构造函数中添加了函数参数作为引用。
但是,在我的课程中存储该功能的最佳方法是什么?
我的直觉是说存储引用是安全的(甚至是const引用)。我希望编译器在编译时为lambda生成代码,并在应用程序运行时将此可执行代码保存在“虚拟”内存中。因此,可执行代码永远不会被“删除”,我可以安全地存储对它的引用。但这是真的吗?
答案 0 :(得分:52)
我可以将函数存储为引用,因为std :: function只是一个函数指针而且函数的'可执行代码'保证留在内存中?
std::function
不仅仅是一个函数指针。它是任意可调用对象的包装器,并管理用于存储该对象的内存。与任何其他类型一样,仅当您有其他方式保证引用对象在使用该引用时仍然有效时,才可安全存储引用。
除非您有充分的理由存储引用,并且保证它仍然有效,否则请按值存储它。
通过const
引用传递给构造函数是安全的,并且可能比传递值更有效。传递非const
引用是一个坏主意,因为它阻止您传递临时,因此用户无法直接传递lambda,bind
的结果或任何其他可调用对象除外std::function<int(int)>
本身。
答案 1 :(得分:13)
如果你通过引用将函数传递给构造函数,并且不复制它,那么当函数超出此对象之外的范围时,你将会失去运气,因为引用将不再有效。在之前的答案中已经说了很多。
我想要添加的是,您可以通过值而不是引用将函数传递给构造函数。为什么?好吧,无论如何你需要它的副本,所以如果你通过值传递,编译器可以在传入临时值时优化制作副本的需要(例如就地编写的lambda表达式)。
当然,无论你做什么,当你将传入函数赋给变量时,你可能会制作另一个副本,所以使用std::move
来消除该副本。例如:
class MyClass
{
public:
typedef std::function<int(int)> MyFunction;
MyClass (Myfunction myFunction): m_myfunction(std::move(myFunction))
{}
private:
MyFunction m_myFunction;
};
因此,如果他们将rvalue传递给上面的代码,编译器会将第一个副本优化到构造函数中,而std :: move会删除第二个副本:)
如果你的(唯一)构造函数采用const引用,将需要在函数中复制它,无论它是如何传入的。
另一种方法是定义两个构造函数,分别处理左值和右值:
class MyClass
{
public:
typedef std::function<int(int)> MyFunction;
//takes lvalue and copy constructs to local var:
MyClass (const Myfunction & myFunction): m_myfunction(myFunction)
{}
//takes rvalue and move constructs local var:
MyClass (MyFunction && myFunction): m_myFunction(std::move(myFunction))
{}
private:
MyFunction m_myFunction;
};
现在,你可以不同地进行rvalue,并且通过显式处理它来消除在这种情况下复制的需要(而不是让编译器为你处理它)。可能比第一个更有效,但也是更多的代码。
(可能在这里看到了相当多的)相关参考(和非常好的阅读): http://cpp-next.com/archive/2009/08/want-speed-pass-by-value/
答案 2 :(得分:3)
作为一般规则(特别是如果您将这些用于某些高度线程化的系统),请按值传递。实际上没有办法从一个线程中验证底层对象是否仍然存在引用类型,因此你可以打开非常讨厌的种族和死锁错误。
另一个考虑因素是std :: function中的任何隐藏状态变量,对于这些变量,修改不太可能是线程安全的。这意味着即使底层函数调用是线程安全的,std :: function包装器&#34;()&#34;呼吁它可能不是。您可以通过始终使用std :: function的线程局部副本来恢复所需的行为,因为它们每个都有一个孤立的状态变量副本。
答案 3 :(得分:2)
尽可能多地复制。它是可复制的。标准库中的大多数算法都需要仿函数。
但是,在非平凡的情况下,通过引用传递可能会更快,因此我建议通过常量引用传递并按值存储,这样您就不必关心生命周期管理。所以:
class MyClass
{
public:
typedef std::function<int(int)> MyFunction;
MyClass (const Myfunction &myFunction);
// ^^^^^ pass by CONSTANT reference.
private:
MyFunction m_myFunction; // Always store by value
};
通过传递常量或右值引用,您可以保证调用者在您仍然可以调用它时不会修改该函数。这可以防止您错误地修改函数,并且通常应该避免这样做,因为它的可读性低于使用返回值。
编辑:我最初在上面说过“CONSTANT或rvalue”,但Dave的评论让我查了一下,而且右边参考不接受左值。
答案 4 :(得分:2)
我建议你复印一份:
MyFunction m_myFunction; //prefferd and safe!
这是安全的,因为如果原始对象超出范围破坏本身,副本仍将存在于类实例中。