Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does it make sense to use move semantics for operator+ and/or operator+=?

I was wondering in what kind of cases it makes sense to use move semantics when overloading operator+ and/or operator+=. Even though it is explained in this question how one could do this, I can't wrap my head around as to why I'd do it. Let's consider operator+=. If I just pass right hand side by reference and make the appropriate changes on the left hand side object, there are no unnecessary copies anyway. So we come back to the same point: Would move semantics be beneficial in such a case?

like image 201
iheap Avatar asked Mar 24 '12 11:03

iheap


1 Answers

Yes and no.

operator+=

Move semantics are not necessarily helpful for operator+= in general, because you are already modifying the left hand side argument (this), so you already have resources to work with most of the times.

Still, as an optimization, it might be worth it. Imagine a std::string implementation whose default constructor does not allocate any memory. Then std::string::operator+=(std::string&&) could simply steal the resources from RHS. Or imagine that the RHS buffer is big enough to hold everything but the LHS is not, then if you can use the RHS buffer you're golden: just swap and prepend.

So, it may be worth it, but you have to study it. Therefore:

  • T& T::operator+=(T const&): always present
  • T& T::operator+=(T&&): to enable move semantics when it makes sense

operator+

Here it is always useful (providing we are talking about classes for which move semantics are useful).

The thing is, operator+ produces a temporary (out of the blue) so it generally has to create resources for this temporary. However if it can steal them rather than create them, it's certainly cheaper.

However, you need not provide all overloads:

  • T operator+(T const&, T const&)
  • T operator+(T&&, T const&)
  • T operator+(T const&, T&&)
  • T operator+(T&&, T&&) (required for disambiguation)

No, you can reuse the same trick that operator= use and create the temporary right in the function signature (by taking one argument by copy). If the type is movable, the move constructor will get called, otherwise it'll be the copy constructor, but since you need the temporary anyway, no loss of performance.

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

Not much of a gain (3 instead of 4) but well, I'll take what I can!

Of course, for string, operator+ is not commutative (which is why it is a bad overload), so the actual implementation of the second overload would require a prepend method.

EDIT: following Move semantics and operator overloading it seems that I was a bit over-enthusiastic. Stealing from Ben Voigt's answer, we get:

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

On the other hand, this seems to only work for commutative operations; - does not work that way but can probably be adapted, / and % on the other hand...

like image 50
Matthieu M. Avatar answered Sep 23 '22 23:09

Matthieu M.