我应该复制一个std :: function还是我总能参考它?

时间:2012-01-03 11:10:31

标签: c++ lambda std-function

在我的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?
   };

如您所见,我在构造函数中添加了函数参数作为引用。

但是,在我的课程中存储该功能的最佳方法是什么?

  • 我可以将函数存储为引用,因为std :: function只是一个函数指针,函数的'可执行代码'保证留在内存中?
  • 我是否必须制作副本,以防传递lambda并且调用者返回?

我的直觉是说存储引用是安全的(甚至是const引用)。我希望编译器在编译时为lambda生成代码,并在应用程序运行时将此可执行代码保存在“虚拟”内存中。因此,可执行代码永远不会被“删除”,我可以安全地存储对它的引用。但这是真的吗?

5 个答案:

答案 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!

这是安全的,因为如果原始对象超出范围破坏本身,副本仍将存在于类实例中。