在 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
in init + *first
是一个左值。
因此,如果 init + *first
调用一个 operator+
重载参数,它将导致该参数的复制构造
但是在 init + *first
之后不再需要 init
的值,因此将其移至参数中是有意义的。
类似地,通过右值引用获取其第一个参数的 operator+
重载可用于允许通过操作修改参数。
这就是 std::move
在这里取得的成就。
自 C++20 起,标准指定了此行为。
std::move(init) + *first
有时可以生成比 init + *first
更高效的代码,因为它允许 init
被覆盖。但是,由于(如您所观察到的)+
的结果通常是右值,因此无需在第二个 std::move
.
中包装整个表达式
例如,如果您正在累积 std::string
,那么 std::move(init) + *first
可能能够将 *first
附加到保留但尚未使用的 space在 init
的缓冲区中,而不是必须分配一个新的缓冲区,其长度是 init
和 *first
.
的长度之和
我刚刚被提醒,当我昨天写了一个大数加法的例子作为教训时,为什么这样效率更高。
二进制 +
运算符创建并 returns 一个临时对象。对于适合机器寄存器的操作数,这可能是零成本的(最多将之前目标寄存器中的内容溢出到堆栈中),但有时需要创建大型数据结构。这似乎是可能必须实现该用例的模板代码。
但是,如果可以破坏左操作数,+
可以实现为 +=
,覆盖操作数并优化新副本的创建。
这种编码风格让我觉得有点奇怪——正如我提到的,+=
具有程序员在这里似乎想要的语义,所以我不确定他们为什么不使用它。
它与 operator +
的 return 值无关,但与它的参数有关。
据我所知,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 重载,这将破坏所有现有用途,就像定义 +
但未定义 +=
的任何类型一样。
在我的 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
in init + *first
是一个左值。
因此,如果 init + *first
调用一个 operator+
重载参数,它将导致该参数的复制构造
但是在 init + *first
之后不再需要 init
的值,因此将其移至参数中是有意义的。
类似地,通过右值引用获取其第一个参数的 operator+
重载可用于允许通过操作修改参数。
这就是 std::move
在这里取得的成就。
自 C++20 起,标准指定了此行为。
std::move(init) + *first
有时可以生成比 init + *first
更高效的代码,因为它允许 init
被覆盖。但是,由于(如您所观察到的)+
的结果通常是右值,因此无需在第二个 std::move
.
例如,如果您正在累积 std::string
,那么 std::move(init) + *first
可能能够将 *first
附加到保留但尚未使用的 space在 init
的缓冲区中,而不是必须分配一个新的缓冲区,其长度是 init
和 *first
.
我刚刚被提醒,当我昨天写了一个大数加法的例子作为教训时,为什么这样效率更高。
二进制 +
运算符创建并 returns 一个临时对象。对于适合机器寄存器的操作数,这可能是零成本的(最多将之前目标寄存器中的内容溢出到堆栈中),但有时需要创建大型数据结构。这似乎是可能必须实现该用例的模板代码。
但是,如果可以破坏左操作数,+
可以实现为 +=
,覆盖操作数并优化新副本的创建。
这种编码风格让我觉得有点奇怪——正如我提到的,+=
具有程序员在这里似乎想要的语义,所以我不确定他们为什么不使用它。
它与 operator +
的 return 值无关,但与它的参数有关。
据我所知,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 重载,这将破坏所有现有用途,就像定义 +
但未定义 +=
的任何类型一样。