【问题标题】:Use of std::move in std::accumulate在 std::accumulate 中使用 std::move
【发布时间】:2022-01-01 20:35:54
【问题描述】:

在我的 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的用途是什么?

【问题讨论】:

    标签: c++ move


    【解决方案1】:

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

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

    【讨论】:

    • 有趣的是,虽然它是使用 + 而不是 init += *first; 定义的,但这不应该产生与带有 move 的版本相似的性能吗?真的有一个有效的案例来选择 + 和 move 而不是 += 还是只是一个设计决定?
    • @alagner std::accumulate 不能使用+=,因为它不能保证a += b 一定具有与a = a + b 相同的语义,因为用户可以重载这些运算符,但是它们希望。如果您想使用+= 编写自己的版本,这是一个不错的选择,但您需要记录它。如果您正在编写仅适用于std::string 的非泛型版本,那么您可能根本不会使用+ 或+=,因为有更有效的连接方法。
    • @BrianBi 这是一个“医生,如果我这样做会很痛”的问题。如果用户将operator += 定义为具有与a=a+b 不同的语义,他们将得到应得的结果。
    • @Spencer 假装有一个重载,BinaryOperation 默认为std::plus 如果有std::plus_assign,我们可能会有不同的std::accumulate
    • @Caleth 这是一个很多更好的论点。甚至可能是一个答案。
    【解决方案2】:

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

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

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

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

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

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

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

    【讨论】:

    • 但是operator + 取消了任何这种效果。它将返回一个将被复制的纯右值。
    • @Spencer 如果operator+ 按值返回,则std::move(init) + *first 是纯右值表达式(独立于std::move),因此= 将使用移动赋值(如果该类型可用)。最后,只需要移动,不需要复制。
    【解决方案3】:

    这与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;
    }
    

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

    【讨论】:

      【解决方案4】:

      我昨天写了一个bignum加法的例子作为教训的时候才被提醒,为什么这样更有效。

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

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

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

      【讨论】:

      • @YSC 我已经删除了误导性的句子。
      • 由于 cmets 已被清理,反对票的原因是我最初将可扩展模板参数称为“宏”。在 C++ 中,该术语通常仅用于预处理器宏
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2023-03-09
      • 1970-01-01
      • 1970-01-01
      • 2020-12-23
      • 1970-01-01
      • 2013-05-18
      • 1970-01-01
      相关资源
      最近更新 更多