【问题标题】:Why isn't std::function template constructor using universal reference?为什么 std::function 模板构造函数不使用通用引用?
【发布时间】:2019-07-25 10:40:28
【问题描述】:

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

从 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。这个例子代表了大多数情况,你同意吗?

使用通用参考:

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

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

按值传递:

左值案例:移动和复制

右值案例:移动

这个分类是否完整?如果是这样,我们可以得出结论,通用引用并不比按值传递更糟糕。然后回到原来的问题。

再次编辑:

也许没有捕获的 lambda 是最常见的情况,其中按值传递实际上是什么都不传递,而按引用传递是传递一个指针。这可能是关键。 LWG 2774 与这个问题有关。在 cmets 中查看。

【问题讨论】:

  • 在这个问题 (stackoverflow.com/questions/21605579/…) 中,我评论说 const 左值引用和右值引用构造函数一起提供了与非引用构造函数相同的性能。既然通用引用可以处理左值和右值引用,为什么不使用它呢?
  • 指定从move(f)构造。所以没有副本。
  • "我评论说 const 左值引用和右值引用构造函数一起提供了至少与非引用构造函数相同的性能" - 你以 std::string 为例.它不一定适用于每种类型。
  • @L.F.参数f上是否使用movef的初始化是否涉及复制构造无关。
  • 这是LWG 2774

标签: c++ c++11


【解决方案1】:

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

如果你传递一个原语,比如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 你想让步。但是,移动应该是超级便宜的,所以我们并不担心。如果你不想让步,那么突然间你必须在调用点检查对象副本的复杂性。啊!

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

【讨论】:

  • 那么按值传递总是比通用引用更好吗?如果是的话,通用引用是什么意思?
  • @jerry_fuyi 你似乎有在不应该添加的地方添加 always 的习惯。您是如何推断出通用参考总是最好的?
  • @jerry_fuyi 我同意 Fureeish 的观点——你需要非常小心地阅读针对特定场景的建议,然后想象其中包含“始终”这个词。没有“总是”。您使用正确的工具来完成这项工作。通用引用可能很有用 - 在这种情况下不是因为函数体总是会移开它的参数。
  • @jerry_fuyi 开头还有一个“so” ;)
  • @jerry_fuyi 开头还有“so”,表示你推断出以下断言。
【解决方案2】:

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

你为什么这么认为?你量过吗?如果我们谈论通过int 怎么办?参考(通用/转发与否)并不总是能提高性能。

是不是搞错了?

不,按值传递函数对象是标准约定。它们的目的是复制成本低,在实现自己的函数对象时,您应该牢记这一点。

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

最终,为什么会这样? :)

【讨论】:

  • stackoverflow.com/questions/21605579/… 第一个答案下面是我的评论,它分析了性能。对吗?
  • @jerry_fuyi std::string 不是C++ 中的唯一类型。不要假设当某件事适用于它时,它也适用于其他类型。
  • 我的评论是否适用于使用 pimpl 成语的所有类型?
  • @jerry_fuyi 不,因为 pimpl idiom 并不表示移动或复制该类型是便宜还是昂贵。你不能这样概括它。
  • std::function 本身甚至没有完全遵循“按值传递函数对象的标准约定”。考虑它的赋值运算符。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-01-21
  • 1970-01-01
  • 2018-09-20
  • 2022-10-20
  • 1970-01-01
  • 2023-03-16
  • 1970-01-01
相关资源
最近更新 更多