The current consensus is that you should implement first all your ?= operators that do not create new objects. Depending on whether exception safety is a problem (in your case it probably is not) or a goal the definition of ?= operator can be different. After that you implement operator? as a free function in terms of the ?= operator using pass-by-value semantics.
// thread safety is not a problem
class Q
{
double w,x,y,z;
public:
// constructors, other operators, other methods... omitted
Q& operator+=( Q const & rhs ) {
w += rhs.w;
x += rhs.x;
y += rhs.y;
z += rhs.z;
return *this;
}
};
Q operator+( Q lhs, Q const & rhs ) {
lhs += rhs;
return lhs;
}
This has the following advantages:
- Only one implementation of the logic. If the class changes you only need to reimplement operator?= and operator? will adapt automatically.
- The free function operator is symmetric with respect to implicit compiler conversions
- It is the most efficient implementation of operator? you can find with respect to copies
Efficiency of operator?
When you call operator? on two elements, a third object must be created and returned. Using the approach above, the copy is performed in the method call. As it is, the compiler is able to elide the copy when you are passing a temporary object. Note that this should be read as 'the compiler knows that it can elide the copy', not as 'the compiler will elide the copy'. Mileage will vary with different compilers, and even the same compiler can yield different results in different compilation runs (due to different parameters or resources available to the optimizer).
In the following code, a temporary will be created with the sum of a
and b
, and that temporary must be passed again to operator+
together with c
to create a second temporary with the final result:
Q a, b, c;
// initialize values
Q d = a + b + c;
If operator+
has pass by value semantics, the compiler can elide the pass-by-value copy (the compiler knows that the temporary will get destructed right after the second operator+
call, and does not need to create a different copy to pass in)
Even if the operator?
could be implemented as a one line function (Q operator+( Q lhs, Q const & rhs ) { return lhs+=rhs; }
) in the code, it should not be so. The reason is that the compiler cannot know whether the reference returned by operator?=
is in fact a reference to the same object or not. By making the return statement explicitly take the lhs
object, the compiler knows that the return copy can be elided.
Symmetry with respect to types
If there is an implicit conversion from type T
to type Q
, and you have two instances t
and q
respectively of each type, then you expect (t+q)
and (q+t)
both to be callable. If you implement operator+
as a member function inside Q
, then the compiler will not be able to convert the t
object into a temporary Q
object and later call (Q(t)+q)
as it cannot perform type conversions in the left hand side to call a member function. Thus with a member function implementation t+q
will not compile.
Note that this is also true for operators that are not symmetric in arithmetic terms, we are talking about types. If you can substract a T
from a Q
by promoting the T
to a Q
, then there is no reason not to be able to substract a Q
from a T
with another automatic promotion.