75

在我的 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 并且调用者返回,我是否必须制作副本?

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

4

5 回答 5

74

我可以将函数存储为引用,因为 std::function 只是一个函数指针,并且函数的“可执行代码”保证保留在内存中吗?

std::function不仅仅是一个函数指针。它是任意可调用对象的包装器,并管理用于存储该对象的内存。与任何其他类型一样,只有当您有其他方法来保证所引用的对象在使用该引用时仍然有效时,才可以安全地存储引用。

除非您有充分的理由来存储引用,并且有办法保证它仍然有效,否则按值存储它。

通过const引用传递给构造函数是安全的,并且可能比传递值更有效。通过非const引用传递是一个坏主意,因为它会阻止您传递临时对象,因此用户不能直接传递 lambda、 的结果或除自身之外的bind任何其他可调用对象。std::function<int(int)>

于 2012-01-03T11:19:38.903 回答
20

如果您通过引用将函数传递给构造函数,并且不对其进行复制,那么当函数超出此对象之外的范围时,您将不走运,因为引用将不再有效。前面的答案已经说了这么多。

我想补充的是,相反,您可以通过而不是引用将函数传递给构造函数。为什么?好吧,无论如何您都需要它的副本,因此如果您按值传递,编译器可以优化在传入临时值时制作副本的需要(例如就地编写的 lambda 表达式)。

当然,无论您做什么,当您将传入的函数分配给变量时,您可能会制作另一个副本,因此请使用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 引用,则无论它是如何传入的,您需要在函数中对其进行复制。

另一种方法是定义两个构造函数,分别处理左值和右值:

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/

于 2013-04-19T11:53:04.243 回答
3

尽可能多地复制。它是可复制的。标准库中的大多数算法都需要仿函数。

但是,在非平凡的情况下,通过引用传递可能会更快,所以我建议通过常量引用传递并按值存储,这样您就不必关心生命周期管理。所以:

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
};

通过传递常量或右值引用,您向调用者保证在您仍然可以调用它时不会修改该函数。这可以防止您错误地修改函数,并且通常应该避免故意这样做,因为它比使用返回值可读性差。

编辑:我最初在上面说“常量或右值”,但戴夫的评论让我查了一下,确实右值引用不接受左值。

于 2012-01-03T11:19:00.157 回答
3

建议你复印一份:

MyFunction m_myFunction; //prefferd and safe!

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

于 2012-01-03T11:20:27.787 回答
3

作为一般规则(特别是如果您将这些用于某些高度线程化的系统),请按值传递。确实没有办法从线程中验证底层对象是否仍然存在引用类型,因此您将面临非常讨厌的竞争和死锁错误。

另一个考虑因素是 std::function 中的任何隐藏状态变量,对其进行修改不太可能是线程安全的。这意味着即使底层函数调用是线程安全的,std::function 包装器的“()”调用也可能不是。您可以通过始终使用 std::function 的线程本地副本来恢复所需的行为,因为它们每个都有状态变量的独立副本。

于 2013-08-26T18:24:34.637 回答