186

Let's say I have a function which takes an std::function:

void callFunction(std::function<void()> x)
{
    x();
}

Should I pass x by const-reference instead?:

void callFunction(const std::function<void()>& x)
{
    x();
}

Does the answer to this question change depending on what the function does with it? For example, if it is a member function or constructor which stores or initializes the std::function into a data member.

Jan Schultke
  • 17,446
  • 6
  • 47
  • 96
Sven Adbring
  • 1,961
  • 2
  • 12
  • 3
  • 1
    Probably not. I don't know for sure, but I would expect `sizeof(std::function)` to be no more than `2 * sizeof(size_t)`, which is the least size that you'd ever consider for a const reference. – Mats Petersson Aug 21 '13 at 19:01
  • 13
    @Mats: I don't think the size of the `std::function` wrapper is as important as the complexity of copying it. If deep copies are involved, it could be far more expensive than `sizeof` suggests. – Ben Voigt Aug 21 '13 at 19:08
  • Should you `move` the function in? – Yakk - Adam Nevraumont Aug 21 '13 at 19:08
  • `operator()()` is `const` so a const reference should work. But I've never used std::function. – Neel Basu Aug 21 '13 at 19:09
  • @Yakk I just pass a lambda directly to the function. – Sven Adbring Aug 21 '13 at 19:16
  • @BenVoigt: But what is there to copy? `std::function` itself doesn't hold a copy of a class or anything like that, right? – Mats Petersson Aug 21 '13 at 19:17
  • @MatsPetersson If a lambda is involved, yes `function` does hold a copy of the anonymous closure class. And depending on the captured variables, copying the lambda could be quite expensive. – syam Aug 21 '13 at 19:18
  • @syam All right, I will just pass it by const-reference then. – Sven Adbring Aug 21 '13 at 19:23
  • @SvenAdbring Not necessarily. ;) Look at my answer to get a good use case of passing by value, it may help you (I hope) choosing the right one. In C++11 passing by value does not always involve copying! – syam Aug 21 '13 at 19:24
  • @syam Ooh, didn't think of std::move :) That seems to work perfectly, thanks! – Sven Adbring Aug 21 '13 at 19:29

3 Answers3

97

If you want performance, pass by value if you are storing it.

Suppose you have a function called "run this in the UI thread".

std::future<void> run_in_ui_thread( std::function<void()> )

which runs some code in the "ui" thread, then signals the future when done. (Useful in UI frameworks where the UI thread is where you are supposed to mess with UI elements)

We have two signatures we are considering:

std::future<void> run_in_ui_thread( std::function<void()> ) // (A)
std::future<void> run_in_ui_thread( std::function<void()> const& ) // (B)

Now, we are likely to use these as follows:

run_in_ui_thread( [=]{
  // code goes here
} ).wait();

which will create an anonymous closure (a lambda), construct a std::function out of it, pass it to the run_in_ui_thread function, then wait for it to finish running in the main thread.

In case (A), the std::function is directly constructed from our lambda, which is then used within the run_in_ui_thread. The lambda is moved into the std::function, so any movable state is efficiently carried into it.

In the second case, a temporary std::function is created, the lambda is moved into it, then that temporary std::function is used by reference within the run_in_ui_thread.

So far, so good -- the two of them perform identically. Except the run_in_ui_thread is going to make a copy of its function argument to send to the ui thread to execute! (it will return before it is done with it, so it cannot just use a reference to it). For case (A), we simply move the std::function into its long-term storage. In case (B), we are forced to copy the std::function.

That store makes passing by value more optimal. If there is any possibility you are storing a copy of the std::function, pass by value. Otherwise, either way is roughly equivalent: the only downside to by-value is if you are taking the same bulky std::function and having one sub method after another use it. Barring that, a move will be as efficient as a const&.

Now, there are some other differences between the two that mostly kick in if we have persistent state within the std::function.

Assume that the std::function stores some object with a operator() const, but it also has some mutable data members which it modifies (how rude!).

In the std::function<> const& case, the mutable data members modified will propagate out of the function call. In the std::function<> case, they won't.

This is a relatively strange corner case.

You want to treat std::function like you would any other possibly heavy-weight, cheaply movable type. Moving is cheap, copying can be expensive.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • The semantic advantage of "pass by value if you are storing it", as you say, is that by contract the function can't keep the address of the argument passed. But is it really true that "Barring that, a move will be as efficient as a const&"? I always see the cost of a copy operation plus the cost of the move operation. With passing by `const&` I only see the cost of the copy operation. – ceztko May 08 '17 at 13:59
  • 2
    @ceztko In both (A) and (B) cases, the temporary `std::function` is created from the lambda. In (A), the temporary is elided into the argument to `run_in_ui_thread`. In (B) a reference to said temporary is passed to `run_in_ui_thread`. So long as your `std::function`s are created from lambdas as temporaries, that clause holds. The previous paragraph deals with the case where the `std::function` persists. If we *are not* storing, just creating from a lambda, `function const&` and `function` have the exact same overhead. – Yakk - Adam Nevraumont May 08 '17 at 14:04
  • Ah, I see! This of course depends of what happens outside of `run_in_ui_thread()`. Is there just a signature to say "Pass by reference, but I won't store the address"? – ceztko May 08 '17 at 14:17
  • @ceztko No, there is not. – Yakk - Adam Nevraumont May 08 '17 at 14:22
  • 3
    @Yakk-AdamNevraumont if would be more complete to cover another option to pass by rvalue ref: `std::future run_in_ui_thread( std::function&& )` – Pavel P Aug 13 '18 at 04:22
  • Would you please introduce some sources or books to get to know how std::function is working behind the scenes. I still don't get your explanations. Thanks – Sami Jul 26 '20 at 00:31
  • 1
    @passionateProgrammer Better late than never. https://stackoverflow.com/questions/18453145/how-is-stdfunction-implemented – Yakk - Adam Nevraumont Feb 26 '22 at 02:05
38

If you're worried about performance, and you aren't defining a virtual member function, then you most likely should not be using std::function at all.

Making the functor type a template parameter permits greater optimization than std::function, including inlining the functor logic. The effect of these optimizations is likely to greatly outweigh the copy-vs-indirection concerns about how to pass std::function.

Faster:

template<typename Functor>
void callFunction(Functor&& x)
{
    x();
}
Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • 2
    I'm not worried at all about performance actually. I just thought using const-references where they should be used is common practice (strings and vectors come to mind). – Sven Adbring Aug 21 '13 at 19:14
  • 17
    @Ben: I think the most modern hippie-friendly way of implementing this is to use `std::forward(x)();`, to preserve the value category of the functor, since it's a "universal" reference. Not going to make a difference in 99% of the cases, though. – GManNickG Aug 21 '13 at 19:29
  • Nice one, I wasn't aware that this would admit Lambda functions. – Emily L. Oct 13 '14 at 13:12
  • 1
    @Ben Voigt so for your case, would I call the function with a move? `callFunction(std::move(myFunctor));` – arias_JC Dec 14 '17 at 15:22
  • 2
    @arias_JC: If the parameter is a lambda, it's already an rvalue. If you have a lvalue, you can either use `std::move` if you will no longer need it in any other way, or pass directly if you don't want to move out of the existing object. Reference-collapsing rules ensure that `callFunction()` has a parameter of type `T&`, not `T&&`. – Ben Voigt Dec 14 '17 at 16:54
  • Nice! @BenVoigt would you mind updating this to (1) incorporate the suggestion from @GManNickG, and (2) expand for the case of passing arguments to `x`. – BoltzmannBrain Apr 24 '18 at 17:56
  • 1
    @BoltzmannBrain: I've chosen not to make that change because it's only valid for the simplest case, when the function is called only once. My answer is to the question of "how should I pass a function object?" and not limited to a function that does nothing besides unconditionally invoke that functor exactly once. – Ben Voigt Apr 24 '18 at 19:47
  • @GManNickG We can go further and use `std::invoke(std::forward(x))` now – L. F. Sep 16 '19 at 13:58
33

As usual in C++11, passing by value/reference/const-reference depends on what you do with your argument. std::function is no different.

Passing by value allows you to move the argument into a variable (typically a member variable of a class):

struct Foo {
    Foo(Object o) : m_o(std::move(o)) {}

    Object m_o;
};

When you know your function will move its argument, this is the best solution, this way your users can control how they call your function:

Foo f1{Object()};               // move the temporary, followed by a move in the constructor
Foo f2{some_object};            // copy the object, followed by a move in the constructor
Foo f3{std::move(some_object)}; // move the object, followed by a move in the constructor

I believe you already know the semantics of (non)const-references so I won't belabor the point. If you need me to add more explanations about this, just ask and I'll update.

syam
  • 14,701
  • 3
  • 41
  • 65