我应该复制一个std ::函数,或者我总是可以参考它吗?
在我的C ++应用程序(使用Visual Studio 2010)中,我需要存储一个std ::函数,如下所示:
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? };
正如你所看到的,我在构造函数中添加了函数参数作为参考。
但是,将函数存储在我的类中的最佳方法是什么?
- 我可以存储函数作为参考,因为std :: function只是一个函数指针,并保证函数的“可执行代码”保留在内存中?
- 如果lambda传递并且调用者返回,是否必须创build副本?
我的直觉告诉我们,存储一个引用(甚至是一个const引用)是安全的。 我希望编译器在编译时为lambda生成代码,并在应用程序运行时将此可执行代码保存在“虚拟”内存中。 因此,可执行代码永远不会被“删除”,我可以安全地存储对它的引用。 但这是真的吗?
我可以存储函数作为参考,因为std :: function只是一个函数指针,并保证函数的“可执行代码”保留在内存中?
std::function
不仅仅是一个函数指针。 它是任意可调用对象的包装,并pipe理用于存储该对象的内存。 和任何其他types一样, 只有当您有其他方法来保证所引用的对象在使用该引用时仍然有效时才可以存储引用。
除非你有一个很好的理由来存储一个引用,并且保证这个引用仍然有效,那么就把它存储起来。
将const
引用传递给构造函数是安全的,并且可能比传递值更高效。 由非const
引用传递是一个坏主意,因为它阻止你传递一个临时的消息,所以用户不能直接传递lambda, bind
的结果,或者除std::function<int(int)>
之外的其他可调用对象std::function<int(int)>
本身。
如果通过引用将该函数传递给构造函数,并且不复制该函数,那么当该函数超出该范围之外的范围时,您将不幸运,因为该引用将不再有效。 以前的答案已经说了很多。
我想补充的是,相反,你可以通过值 ,而不是引用,将函数传递给构造函数。 为什么? 好吧,无论如何你都需要它的副本,所以如果你按值传递的话,编译器可以优化临时传入时(例如就地写入的lambdaexpression式)的复制需求。
当然,无论你做什么,当你把传入的函数分配给variables时,你可能会做另一个副本,所以使用std::move
来消除这个副本。 例:
class MyClass { public: typedef std::function<int(int)> MyFunction; MyClass (Myfunction myFunction): m_myfunction(std::move(myFunction)) {} private: MyFunction m_myFunction; };
所以,如果他们传入上面的右值,编译器优化掉第一个拷贝到构造函数中,而std :: move则删除第二个拷贝:)
如果你的(只)构造函数接受一个const引用,你将需要在函数中复制它,而不pipe它是如何传入的。
另一种方法是定义两个构造函数,分别处理左值和右值:
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; };
现在,你可以不同的方式对它进行不同的处理,并且在这种情况下通过显式处理来消除复制的需要(而不是让编译器为你处理)。 可能比第一个更有效率,但也是更多的代码。
(可能在这里可以看到相当一部分)相关的参考资料(和一个很好的阅读): http : //cpp-next.com/archive/2009/08/want-speed-pass-by-value/
作为一个通用规则(特别是如果你使用这些高度线程的系统),通过价值。 真的没有办法从一个线程内核实底层对象是否仍然在引用types中,所以你打开了自己的非常讨厌的种族和死锁的错误。
另一个考虑是std :: function中隐藏的状态variables,对于它们来说修改是不太可能是线程安全的。 这意味着,即使基础函数调用是线程安全的,std :: function包装的“()”调用可能不会。 您可以通过始终使用std :: function的线程本地副本来恢复所需的行为,因为它们每个都有一个状态variables的独立副本。
我build议你做一个副本:
MyFunction m_myFunction; //prefferd and safe!
这是安全的,因为如果原始对象超出了范围自身的破坏,副本将仍然存在于类实例中。
只要你喜欢复制。 它是可复制的。 标准库中的大部分algorithm都需要函子。
但是,在非平凡的情况下,通过引用传递可能会更快,所以我build议通过常量引用传递并按值存储,因此您不必关心生命周期pipe理。 所以:
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的评论使我查了一下,确实右值引用不接受左值。