在 std::accumulate 中使用 std::move

cur*_*y12 26 c++ move-semantics stdmove

在我的 Fedora 34 环境(g++)中,std::accumulate定义为:

template<typename ITER, typename T>
constexpr inline T accumulate(ITER first, ITER last, T init)
{
  for (; first != last; ++first)
      init = std::move(init) + *first; // why move ?

  return init;
}
Run Code Online (Sandbox Code Playgroud)

如果表达式init + *first已经是右值,那么 的目的是什么std::move

Bri*_*ian 25

std::move(init) + *first有时可以生成比 更有效的代码init + *first,因为它允许init被覆盖。但是,由于(正如您所观察到的) 的结果+通常是右值,因此无需将整个表达式包装在第二个 中std::move

例如,如果您正在累积std::strings,则std::move(init) + *first可能能够追加*first到 s 缓冲区中保留但尚未使用的空间,init而不必分配一个长度为init和的长度之和的新缓冲区*first

  • @alagner `std::accumulate` 不能使用 `+=`,因为它不能保证 `a += b` 一定与 `a = a + b` 具有相同的语义,因为用户可以重载这些运营商随心所欲。如果您想使用“+=”编写自己的版本,这是一个不错的选择,但您需要记录它。如果您正在编写仅适用于“std::string”的非通用版本,那么您可能根本不会使用“+”或“+=”,因为有更有效的连接方法。 (5认同)
  • 有趣的是,它是使用 `+` 定义的,而不是 `init += *first;`,这难道不会产生与 move 版本类似的性能吗?是否真的有一个有效的案例来选择“+”和“move”而不是“+=”,或者这只是一个设计决策? (2认同)
  • @Spencer 假装有一个重载,并且 `BinaryOperation` 默认为 `std::plus` 如果有 `std::plus_assign` 我们可能有一个不同的 `std::accumulate` (2认同)

use*_*522 21

的值类别init + *first并不重要。

initininit + *first是左值。

因此,如果init + *first调用operator+按值获取参数的重载,它将导致该参数的复制构造

init但在 后不再需要的值init + *first,因此将其移至参数中是有意义的。

类似地,operator+通过右值引用获取其第一个参数的重载可用于允许操作修改参数。

这就是std::move这里所取得的成就。

该标准自 C++20 起指定了此行为。


Cal*_*eth 5

它与 的返回值无关operator +,而是与它的参数有关。

据我所知,一个有效的实现std::accumulate可能是

template<typename ITER, typename T, typename OP = plus>
constexpr inline T accumulate(ITER first, ITER last, T init, OP op = {})
{
  for (; first != last; ++first)
      init = op(std::move(init), *first); // move the argument that is being overwritten

  return init;
}
Run Code Online (Sandbox Code Playgroud)

accumulate用 来指定将是一个更大的重大更改operator +=。为了对称性,您需要更改BinaryOperation重载,这将破坏所有现有用途,就像定义+但未定义的任何类型一样+=