[英]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.