简体   繁体   中英

std::function - value vs. reference argument

Are there any practical differences between std::function for type with value parameter vs with const reference to value parameter? Consider following code:

auto foo = [] (VeryBigType i) {     
};

auto bar = [] (const VeryBigType& i) {
};

std::function<void(VeryBigType)> a;
a = foo;
a = bar;

std::function<void(const VeryBigType&)> b;
b = foo;
b = bar;

This code compiles without issues and works perfeclty well. I know that passing by value vs by ref has performance differences and so foo and bar would behave differently. But are there any differences depending on std::function template type? For example, are there any implementation and/or behaviour and/or performance differences between std::function<void(VeryBigType)>(bar) vs std::function<void(const VeryBigType&)>(bar) or these constructs are equivalent?

cppreference says that std::function<R(Args...)>::operator() has signature

R operator()(Args... args) const;

and that it calls the stored callable f basically by f(std::forward<Args>(args)...) . The performance characteristics depend on both the template argument and the lambda's argument type and I think it would be helpful to just see everything that can happen. In your case, you have 2 std::function types, 2 callables, and 3 possible value categories for the argument, giving you 12 possibilities.

  • std::function<void(VeryBigType)> f = [](VeryBigType i) { }

    • If you call this with an lvalue, like

      VeryBigType v; f(v);

      This will copy v into the argument of operator() , and then operator() will pass an rvalue to the lambda, which will move the value into i . Total cost: 1 copy + 1 move

    • If you call this with a prvalue, like

      f(VeryBigType{});

      Then this will materialize the prvalue into the argument of operator() , then pass an rvalue to the lambda, which will move it into i . Total cost: 1 move

    • If you call this with an xvalue, like

      VeryBigType v; f(std::move(v));

      This will move v into the argument of operator() , which will pass an rvalue to the lambda, which will move it again into i . Total cost: 2 moves.

  • std::function<void(VeryBigType)> f = [](VeryBigType const &i) { }

    • If you call this with an lvalue, this will copy once into the argument of operator() , and then the lambda will be given a reference to that argument. Total cost: 1 copy.

    • If you call this with a prvalue, this will materialize it into the argument of operator() , which will pass a reference to that argument to the lambda. Total cost: nothing.

    • If you call this with an xvalue, this will move it into the argument of operator() , which will pass a reference to that argument to the lambda. Total cost: 1 move.

  • std::function<void(VeryBigType const&)> f = [](VeryBigType i) { }

    • If you call this with an lvalue or xvalue (ie with a glvalue), operator() will receive a reference to it. If you call this with a prvalue, it will be materialized into a temporary, and operator() will receive a reference to that. In any case, the inner call to the lambda will always copy. Total cost: 1 copy.
  • std::function<void(VeryBigType const&)> f = [](VeryBigType const &i) { }

    • Again, no matter what you call this with, operator() will receive just a reference to it, and the lambda will just receive the same reference. Total cost: nothing.

So, what did we learn? If both the std::function and the lambda take references, you avoid any extraneous copies and moves. Use this when possible. Putting a by-value lambda inside a by- const -lvalue-reference std::function , however, is a bad idea (unless you have to). Essentially, the lvalue reference "forgets" the value category of the argument, and the argument to the lambda is always copied. Putting a by- const -lvalue-reference lambda inside a by-value std::function is pretty good performance-wise, but you only need to do so if you're calling into other code that expects a by-value std::function , because otherwise a by-reference std::function achieves the same thing but with less copying and moving. Putting a by-value lambda inside a by-value std::function is slightly worse than putting a by- const -lvalue-reference lambda inside of it, due to an extra move in all calls. It would be better to instead take the argument of the lambda by-rvalue-reference, which is pretty much the same as taking it by- const -lvalue-reference except you still can mutate the argument, just as if you took it by value anyway.

TL;DR: By-value and rvalue-reference arguments in a std::function template argument should correspond to by-rvalue-reference or by- const -lvalue-reference arguments in the lambda you put inside the std::function . By-lvalue-reference arguments in the type should correspond to by-lvalue-reference arguments in the lambda. Anything else incurs additional copies or moves, and should only be used when needed.

Your question is confusing, because you seem to be aware that there are very large performance differences between a value and a ref argument.

What you seem to not be aware of is that the template type of the function object is what determines how arguments are passed to your lambda function, not the lambda function itself, because of the cdecl calling convention: the caller passes arguments on the stack and then performs the cleanup, and the caller calls through your std::function object.

So, a will always allocate a new copy of your object and pass a reference to it, then clean it up, and b will always pass a reference to the original object.

Edit: as to why that works regardless of how you define the lambda functions, again because of cdecl , both functions expect a pointer as the first argument and do their work on them. The rest of the declarations around the types (type sizes, constness, references etc) are only used to validate the code inside the function and validating that the function itself can be called by your function object (ie, that function will send a pointer as the first argument).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM