Should I copy an std::function or can I always tak

2019-01-16 22:42发布

问题:

In my C++ application (using Visual Studio 2010), I need to store an std::function, like this:

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

As you can see, I added the function argument as a reference in the constructor.

But, what is the best way to store the function in my class?

  • Can I store the function as a reference since std::function is just a function-pointer and the 'executable code' of the function is guaranteed to stay in memory?
  • Do I have to make a copy in case a lambda is passed and the caller returns?

My gut feeling says that it's safe to store a reference (even a const-reference). I expect the compiler to generate code for the lambda at compile time, and keep this executable code in 'virtual' memory while the application is running. Therefore the executable code is never 'deleted' and I can safely store a reference to it. But is this really true?

回答1:

Can I store the function as a reference since std::function is just a function-pointer and the 'executable code' of the function is guaranteed to stay in memory?

std::function is very much not just a function pointer. It's a wrapper around an arbitrary callable object, and manages the memory used to store that object. As with any other type, it's safe to store a reference only if you have some other way to guarantee that the referred object is still valid whenever that reference is used.

Unless you have a good reason for storing a reference, and a way to guarantee that it remains valid, store it by value.

Passing by const reference to the constructor is safe, and probably more efficient than passing a value. Passing by non-const reference is a bad idea, since it prevents you from passing a temporary, so the user can't directly pass a lambda, the result of bind, or any other callable object except std::function<int(int)> itself.



回答2:

If you pass the function in to the constructor by reference, and don't make a copy of it, you'll be out of luck when the function goes out of scope outside of this object, as the reference will no longer be valid. That much has been said in the previous answers already.

What I wanted to add was that, instead, you could pass the function by value, not reference, into the constructor. Why? well, you need a copy of it anyway, so if you pass by value the compiler can optimize away the need to make a copy when a temporary is passed in (such as a lambda expression written in-place).

Of course, however you do things, you potentially make another copy when you assign the passed in function to the variable, so use std::move to eliminate that copy. Example:

class MyClass
{
public:
   typedef std::function<int(int)> MyFunction;

   MyClass (Myfunction myFunction): m_myfunction(std::move(myFunction))
       {}

private:
   MyFunction m_myFunction;
};

So, if they pass in an rvalue to the above, the compiler optimises away the first copy into the constructor, and std::move removes the second one :)

If your (only) constructor takes a const reference, you will need to make a copy of it in the function regardless of how it's passed in.

The alternative is to define two constructors, to deal with lvalues and rvalues separately:

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

Now, you handly rvalues differently and eliminate the need to copy in that case by explicitly handling it (rather than letting the compiler handle it for you). May be marginally more efficient than the first but is also more code.

The (probably seen a fair bit around here) relevant reference (and a very good read): http://cpp-next.com/archive/2009/08/want-speed-pass-by-value/



回答3:

As a general rule (especially if you're using these for some highly threaded system), pass by value. There is really no way to verify from within a thread that the underlying object is still around with a reference type, so you open yourself up to very nasty race and deadlock bugs.

Another consideration is any hidden state variables in the std::function, for whom modification is very unlikely to be thread-safe. This means that even if the underlying function call is thread-safe, the std::function wrapper's "()" call around it MAY NOT BE. You can recover the desired behavior by always using thread-local copies of the std::function because they'll each have an isolated copy of the state variables.



回答4:

I would suggest you to make a copy:

MyFunction m_myFunction; //prefferd and safe!

It is safe because if the original object goes out of scope destructing itself, the copy will still exist in the class instance.



回答5:

Copy as much as you like. It is copyable. Most algorithms in standard library require that functors are.

However, passing by reference will probably be faster in non-trivial cases, so I'd suggest passing by constant reference and storing by value so you don't have to care about lifecycle management. So:

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

By passing by constant or rvalue reference you promise the caller that you will not modify the function while you can still call it. This prevents you from modifying the function by mistake and doing it intentionally should usually be avoided, because it's less readable than using return value.

Edit: I originally said "CONSTANT or rvalue" above, but Dave's comment made me look it up and indeed rvalue reference does not accept lvalues.