繁体   English   中英

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

[英]Use of std::move in std::accumulate

在我的 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;
}

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

init + *first的值类别无关紧要。

init init + *first中的 init 是一个左值。

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

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

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

这就是std::move在这里实现的。

该标准从 C++20 开始指定此行为。

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

例如,如果您正在累积std::string s,那么std::move(init) + *first可能能够 append *first进入init缓冲区中保留但尚未使用的空间,而不是拥有分配一个新的缓冲区,其长度是init*first的长度之和。

您没有显示宏扩展为什么,但是当我昨天写了一个 bignum 加法示例作为课程时,我只是被提醒了,为什么这样做更有效。

二进制+运算符创建并返回一个临时 object。 对于适合机器寄存器的操作数,这可以是零成本(最多将之前目标寄存器中的内容溢出到堆栈上),但有时需要创建大型数据结构。 这似乎是可能必须实现该用例的模板代码。

但是,如果可以破坏左操作数,则+可以实现为+= ,覆盖操作数并优化新副本的创建。

这种编码风格让我觉得有点奇怪——正如我所提到的, +=正是程序员在这里想要的语义,所以我不确定他们为什么不使用它。

这与operator +的返回值无关,而是与 arguments 相关。

据我所知, 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;
}

根据operator +=指定accumulate将是一个更大的突破性变化。 为了对称,您需要更改BinaryOperation重载,这将破坏所有现有用途,任何定义+但未定义+=的类型也是如此。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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