我是否应该复制std::函数,或者我可以总是引用它

Should I copy an std::function or can I always take a reference to it?

本文关键字:我可以 或者 引用 函数 是否 复制 std      更新时间:2023-10-16

在我的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传递和调用者返回的情况下复制?

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

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

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

除非你有一个很好的理由来存储引用,并且有一种方法来保证它仍然有效,否则按值存储它。

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

如果您通过引用将函数传递给构造函数,而不创建它的副本,那么当函数超出该对象之外的作用域时,您将不走运,因为引用将不再有效。在前面的回答中已经说了很多了。

我想添加的是,相反,您可以通过而不是引用将函数传递给构造函数。为什么?好吧,你无论如何都需要它的副本,所以如果你按值传递,编译器可以优化,当传入临时对象时(比如就地编写的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删除第二个副本:)

如果(only)构造函数接受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/

我建议你复制一份:

MyFunction m_myFunction; //prefferd and safe!

它是安全的,因为如果原始对象超出了作用域并破坏了它自己,那么它的副本仍然存在于类实例中。

随心所欲地复制。它是可复制的。标准库中的大多数算法都要求函子是。

然而,在一些重要的情况下,按引用传递可能会更快,所以我建议按常量引用传递,按值存储,这样你就不必关心生命周期管理了。所以:

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

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

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

作为一般规则(特别是如果您在一些高线程系统中使用这些规则),按值传递。实际上没有办法从线程内部验证底层对象是否仍然存在引用类型,因此您将自己暴露在非常讨厌的竞争和死锁错误中。

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

相关文章: