operator+和/或operator+=使用移动语义是否有意义?

8

我想知道在重载operator+和/或operator+=时,什么情况下使用移动语义是有意义的。虽然这个问题中已经解释了如何做到这一点,但我无法理解为什么要这样做。让我们考虑operator+=。如果我只是通过引用传递右侧,并对左侧对象进行适当的更改,则根本没有不必要的副本。因此,我们回到同一个问题:在这种情况下使用移动语义是否有益呢?


重载运算符通常不是一个好主意。因为:1) 如果显然有一个很好的情况需要这样做,那么可能已经有一个库为你实现了。 2) 如果不明显需要这样做,那么很可能这样做是非常糟糕的情况。 - enobayram
3
如果您对重载运算符感到不舒服,可以将其变成一个"append"成员函数。现在您仍然可以回答这个问题。 - R. Martinho Fernandes
7
@enobayram,这根本没有意义。按照这个逻辑,编写任何类型的代码都是一个不好的主意,永远不应该这样做。 - TonyK
@enobayram:这要看情况。对于 +,我可能同意(不过,为什么不编写一个数字或矩阵库呢?),但 <<>> 只是 C++ 中流的惯用法。 - Matthieu M.
@MatthieuM。有很多非常好的、宽容的开源矩阵库实现,这是类型1)的最佳示例之一,至于<<>>,那是一种特殊情况,你可以重载运算符以符合库(伪代码1))。 - enobayram
显示剩余4条评论
2个回答

13

是和不是。

operator+=

移动语义通常对于operator+=并没有什么帮助,因为您已经修改了左侧参数(this),所以大多数情况下您已经有资源可供使用。

但是,作为一种优化,它可能是值得的。想象一个std::string实现,其默认构造函数不分配任何内存。然后,std::string::operator+=(std::string&&)可以简单地从RHS中窃取资源。或者想象一下,RHS缓冲区足够大,可以容纳所有内容,但LHS不行,那么如果您可以使用RHS缓冲区,您就可以轻松解决:只需交换并添加前缀。

因此,这可能是值得的,但您必须进行研究。因此:

  • T& T::operator+=(T const&):始终存在
  • T& T::operator+=(T&&):在适当时启用移动语义

operator+

如果我们讨论的是适用于移动语义的类,则这里总是有用的。

问题在于,operator+会产生一个临时变量(突然出现),因此它通常必须为此临时变量创建资源。但是,如果它可以窃取它们而不是创建它们,则肯定更便宜。

但是,您不需要提供所有重载:

  • T operator+(T const&, T const&)
  • T operator+(T&&, T const&)
  • T operator+(T const&, T&&)
  • T operator+(T&&, T&&)(用于消除歧义)

不,您可以重复使用operator=使用的同样技巧,并在函数签名中创建临时变量(通过复制一个参数)。如果该类型可移动,则将调用移动构造函数,否则将调用复制构造函数,但由于您无论如何都需要临时变量,因此不会损失性能。

inline T operator+(T left, T const& right) { left += right; return left; }
inline T operator+(T const& left, T right) { right += left; return right; } // commutative
inline T operator+(T left, T&& right) { left += right; return left; } // disambiguation

虽然只有一个数字的差别(3而不是4),但我会接受这个结果!

当然,对于字符串来说,operator+不是可交换的(这就是为什么它是一个糟糕的重载),因此第二个重载的实际实现需要一个prepend方法。

编辑:根据Move semantics and operator overloading,似乎我过于热情了。从Ben Voigt的答案中借鉴,我们得到:

inline T operator+(T left, T const& right) { left += right; return left; }
inline T operator+(const T& left, T&& right) { right += left; return right; }

另一方面,这似乎只适用于交换操作; - 不起作用,但可能可以适应,另一方面,/% ...

最好做 right += left; return right;,这样在返回时可以将 right 视为右值。 - Johannes Schaub - litb
1
只是想澄清一下:您是否根据@JohannesSchaub-litb的评论更新了答案?我有点困惑。 - iheap
1
@BenVoigt:没错,但有一件事情让我感到困扰。所有的例子都是用可交换操作完成的,但并不是所有的操作都是可交换的。使用operator-时已经出现了问题,而使用/%时则更加糟糕。 - Matthieu M.
@MatthieuM.: 是的,因为使用 - 可以(通常)执行 right = -right; right += left;。并且通过 / 可能会使用 right = recip(right); right *= left;。 但是 % 不起作用。或者 <<>>。对于这些情况,您只需省略第二个重载,并且不关心右操作数是否为右值引用。 - Ben Voigt
1
@Moia:据我所知,移动语义或运算符没有任何变化会影响这个答案;因此,我仍然认为它是准确的(无论好坏)。不过,我猜两个运算符也还好。 - Matthieu M.
显示剩余4条评论

1
如果您要附加两个字符串、向量等无法“移动”的对象,这是没有意义的。但是,如果您要附加链接列表(其中附加列表可能是O(1)运算符),如果您愿意牺牲右侧,则有意义。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接