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

时间:2019-07-25 10:40:28

标签: c++ c++11

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

从cppreference(https://en.cppreference.com/w/cpp/utility/functional/function/function)中,我们可以看到std::function的模板构造函数未使用引用。

template< class F > function( F f );

这是一个错误吗?如果不是,为什么标准不要求构造函数使用通用引用?

编辑:

让我们考虑两个案例。

  1. F是内置类型,这里是一个大小为4个字节的函数指针。

使用通用参考:

左值的情况:复制了4个字节。

右值的情况:复制了4个字节。 (您将std::move应用于内置类型吗?)

传递值:

所有情况:复制4个字节。

  1. F是具有非平凡副本构造函数的类型,例如。一个lambda类型,可按值捕获std::string。此示例代表大多数情况,您是否同意?

使用通用参考:

左值案例:复制构造函数被调用一次

右值案例:move构造函数被调用一次

传递值:

左值案例:一个动作和一个副本

右值案例:一个举动

此分类是否完整?如果是这样,我们可以得出这样的结论,即普遍参照并不比按价值传递更糟。然后返回到原始问题。

再次编辑:

也许没有捕获的lambda是最常见的情况,其中按值传递实际上不传递任何东西,而按引用传递则传递指针。这可能是关键。 LWG 2774与这个问题有关。在评论中看到它。

2 个答案:

答案 0 :(得分:4)

  

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

您为什么这么认为?你测量了吗?如果我们正在谈论传递int怎么办?引用(无论是否通用/转发)并不总是可以提高性能。

  

这是一个错误吗?

不,这是按值传递函数对象的标准约定。它们的复制成本很低,在实现自己的功能对象时,请记住这一点。

  

如果没有,为什么标准不要求构造函数使用通用引用?

最终为什么会这样? :)

答案 1 :(得分:4)

因为构造函数移动了其参数,所以接受引用是没有意义的。这归结为关于何时获取价值的常见建议。

如果您传递诸如int之类的原语,则按引用传递是一种悲观化。如果您传递一个复杂类型,例如std::string,则可以将它std::move放入参数中(如果不这样,那是因为无论如何都需要一个副本)。您将两全其美。

// 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

它还向呼叫站点发出信号,除非您明确声明std::move的所有权,否则原始对象将不会被修改。

如果该函数接受了引用,好吧,您可能会保存一个动作 if 。但是,移动应该是非常便宜的,因此我们不必担心。而且,如果您不想让步,那么您突然必须在呼叫站点进行对象副本的复制。 gh!

此模式是充分利用移动语义的关键。