More Effective C++ | Item 22: Consider using op= instead of stand-alone op Back to Item 21: Overload to avoid implicit type conversions Continue to Item 23: Consider alternative libraries Item 22: Consider using op= instead of stand-alone op. Most programmers expect that if they can say things like these, x = x + y; x = x - y; they can also say things like these: x += y; x -= y; If x and y are of a user-defined type, there is no guarantee that this is so. As far as C++ is concerned, there is no relationship between operator+, operator=, and operator+=, so if you want all three operators to exist and to have the expected relationship, you must implement that yourself. Ditto for the operators -, *, /, etc. A good way to ensure that the natural relationship between the assignment version of an operator (e.g., operator+=) and the stand-alone version (e.g., operator+) exists is to implement the latter in terms of the former (see also Item 6). This is easy to do: class Rational { public: ... Rational& operator+=(const Rational& rhs); Rational& operator-=(const Rational& rhs); }; // operator+ implemented in terms of operator+=; see // Item E21 for an explanation of why the return value is // const and page 109 for a warning about implementation const Rational operator+(const Rational& lhs, const Rational& rhs) { return Rational(lhs) += rhs; } // operator- implemented in terms of operator -= const Rational operator-(const Rational& lhs, const Rational& rhs) { return Rational(lhs) -= rhs; } In this example, operators += and -= are implemented (elsewhere) from scratch, and operator+ and operator- call them to provide their own functionality. With this design, only the assignment versions of these operators need to be maintained. Furthermore, assuming the assignment versions of the operators are in the class's public interface, there is never a need for the stand-alone operators to be friends of the class (see Item E19). If you don't mind putting all stand-alone operators at global scope, you can use templates to eliminate the need to write the stand-alone functions: template const T operator+(const T& lhs, const T& rhs) { return T(lhs) += rhs; // see discussion below } template const T operator-(const T& lhs, const T& rhs) { return T(lhs) -= rhs; // see discussion below } ... With these templates, as long as an assignment version of an operator is defined for some type T, the corresponding stand-alone operator will automatically be generated if it's needed. All this is well and good, but so far we have failed to consider the issue of efficiency, and efficiency is, after all, the topic of this chapter. Three aspects of efficiency are worth noting here. The first is that, in general, assignment versions of operators are more efficient than stand-alone versions, because stand-alone versions must typically return a new object, and that costs us the construction and destruction of a temporary (see Items 19 and 20, as well as Item E23). Assignment versions of operators write to their left-hand argument, so there is no need to generate a temporary to hold the operator's return value. The second point is that by offering assignment versions of operators as well as stand-alone versions, you allow clients of your classes to make the difficult trade-off between efficiency and convenience. That is, your clients can decide whether to write their code like this, Rational a, b, c, d, result; ... result = a + b + c + d; // probably uses 3 temporary // objects, one for each call // to operator+ or like this: result = a; // no temporary needed result += b; // no temporary needed result += c; // no temporary needed result += d; // no temporary needed The former is easier to write, debug, and maintain, and it offers acceptable performance about 80% of the time (see Item 16). The latter is more efficient, and, one supposes, more intuitive for assembly language programmers. By offering both options, you let clients develop and debug code using the easier-to-read stand-alone operators while still reserving the right to replace them with the more efficient assignment versions of the operators. Furthermore, by implementing the stand-alones in terms of the assignment versions, you ensure that when clients switch from one to the other, the semantics of the operations remain constant. The final efficiency observation concerns implementing the stand-alone operators. Look again at the implementation for operator+: template const T operator+(const T& lhs, const T& rhs) { return T(lhs) += rhs; } The expression T(lhs) is a call to T's copy constructor. It creates a temporary object whose value is the same as that of lhs. This temporary is then used to invoke operator+= with rhs, and the result of that operation is returned from operator+.8 This code seems unnecessarily cryptic. Wouldn't it be better to write it like this? template const T operator+(const T& lhs, const T& rhs) { T result(lhs); // copy lhs into result return result += rhs; // add rhs to it and return } This template is almost equivalent to the one above, but there is a crucial difference. This second template contains a named object, result. The fact that this object is named means that the return value optimization (see Item 20) was, until relatively recently, unavailable for this implementation of operator+ (see the footnote on page 104). The first implementation has always been eligible for the return value optimization, so the odds may be better that the compilers you use will generate optimized code for it. Now, truth in advertising compels me to point out that the expression return T(lhs) += rhs; is more complex than most compilers are willing to subject to the return value optimization. The first implementation above may thus cost you one temporary object within the function, just as you'd pay for using the named object result. However, the fact remains that unnamed objects have historically been easier to eliminate than named objects, so when faced with a choice between a named object and a temporary object, you may be better off using the temporary. It should never cost you more than its named colleague, and, especially with older compilers, it may cost you less. All this talk of named objects, unnamed objects, and compiler optimizations is interesting, but let us not forget the big picture. The big picture is that assignment versions of operators (such as operator+=) tend to be more efficient than stand-alone versions of those operators (e.g. operator+). As a library designer, you should offer both, and as an application developer, you should consider using assignment versions of operators instead of stand-alone versions whenever performance is at a premium. Back to Item 21: Overload to avoid implicit type conversions Continue to Item 23: Consider alternative libraries 8 At least that's what's supposed to happen. Alas, some compilers treat T(lhs) as a cast to remove lhs's constness, then add rhs to lhs and return a reference to the modified lhs! Test your compilers before relying on the behavior described above. Return