为什么std :: function模板构造函数不使用通用引用?

jer*_*uyi 3 c++ c++11

我认为具有通用引用参数的构造函数比没有引用的构造函数具有更好的性能。

From cppreference (https://en.cppreference.com/w/cpp/utility/functional/function/function), we can see that the template constructor for std::function is not using reference.

template< class F > function( F f );
Run Code Online (Sandbox Code Playgroud)

Is it a mistake? If not, why the standard doesn't requires the constructor use universal reference?

EDIT:

Let's think about two cases.

  1. F is a built-in type, here a function pointer whose size is 4 bytes.

Using universal reference:

lvalue case: 4 bytes are copied.

rvalue case: 4 bytes are copied. (Will you apply a std::move to built-in type?)

Passing by value:

all cases: 4 bytes are copied.

  1. F is a type with non-trivial copy constructor, ex. a lambda type which captures a std::string by value. This example stands for most cases, do you agree?

Using universal reference:

lvalue case: copy constructor is invoked once

rvalue case: move constructor is invoked once

Passing by value:

lvalue case: a move and a copy

rvalue case: a move

Is this classification complete? If so, we can run into the conclusion that universal reference is no worse then passing by value. Then it returns to the original question.

EDIT again:

Maybe lambdas without captures are the most common cases, where passing by value is actually passing nothing while passing by reference is passing a pointer. This may be the key. LWG 2774 is related to this question. See it in comments.

Lig*_*ica 5

Because that constructor moves its argument, accepting a reference is pointless. This comes down to the usual advice on when to take values.

If you pass a primitive, like an int, passing by reference is a pessimisation. If you pass a complex type, like a std::string, you can already std::move it into the argument (and, if you don't, then that's because you wanted a copy anyway). You get the best of both worlds.

// Bog-standard choice between value and ref; original value only observed

void foo(const int& x) { cout << x << '\n'; }
void foo(const int x) { cout << x << '\n'; }  // Better!

void foo(const std::string& x) { cout << x << '\n'; }  // Better!
void foo(const std::string x) { cout << x << '\n'; }


// When we want to store

void storeACopy(int);
void foo(const int& x) { storeACopy(x); }
void foo(const int x) { storeACopy(x); }  // Better!

void storeACopy(std::string);
void foo(const std::string& x) { storeACopy(x); }  // Meh
void foo(std::string x) { storeACopy(std::move(x)); }  // Cheap!


// So, best of both worlds:
template <typename T>
void foo(T x) { storeACopy(std::move(x)); }

// This performs a copy when needed, but allows callsite to
// move instead (i.e. total flexibility) *and* doesn't require silly
// refs-to-primitives
Run Code Online (Sandbox Code Playgroud)

It also signals to the call site that the original object will not be modified unless you specifically yield ownership with std::move.

If instead the function took a reference, okay you'd potentially save one move if you want to yield. But, moves are supposed to be super-cheap so we're not concerned about that. And if you didn't want to yield then suddenly you have to go through the rigmaroll of an object copy at the callsite. Ugh!

This pattern is key to making the most out of move semantics.

  • @jerry_fuyi,您似乎有一个习惯,总是在不应该添加的地方添加*。您如何推断通用参考“总是”是最好的? (3认同)