C++ Howto: Overload Operators

C++ allows you to overload operators. This means, for example, that you can write expressions that are natural, e.g., BigInteger a,b; cout << "enter two integer values "; cin >> a >> b; cout << "a + b = " << (a+b) << endl; Here operators << and + are overloaded for BigInteger values. Of course it's possible to run amok with operator overloading and use + to mean multiply just because you can. Rather than dwell on when to overload operators, this howto will explain how to overload operators. Many books show the syntax for declaring overloaded operators, but few offer guidelines for keeping the amount of code you write to a minimum and for avoiding code duplication. The guidelines in this howto do not necessarily result in the most efficient code from an execution standpoint, but development efforts are minimized --- efficiency and maintainability from a coding standpoint is emphasized. Of course once you've succeed in implementing overloaded operators you can then concentrate on making things efficient. To quote Donald Knuth (from Code Complete by Steve McConnell) premature optimization is the root of all evil

Table of Contents

Arithmetic Operators

Arithmetic operators inlude +=, -=, *=, /=, %= and there binary cousins +, -, *, /, %. The easiest way to implement these operators is to implement the arithmetic assignment operators as member functions, and then to implement the binary operators using the arithmetic assignment functions.

Binary Operators

Here we assume that all arithmetic assignment operators have been implemented, and discuss how to implement the binary arithmetic operators. We'll use + as an example, assuming we're implementing addition for a class BigInt, but the example applies to all the binary arithmetic operators for any class. BigInt operator + (const BigInt & lhs, const BigInt & rhs) // postcondition: returns lhs + rhs { BigInt copy(lhs); copy += rhs; return copy; } The code here is straightfoward. A copy of the parameter lhs (left-hand-side) is made and the sum accumulated in this copy which is then returned. Assuming that += is implemented properly it's possible to shorten the body of the function: BigInt operator + (const BigInt & lhs, const BigInt & rhs) // postcondition: returns lhs + rhs { BigInt copy(lhs); return copy += rhs; }

Why operator + is not a member function (symmetry is good)

In some texts/explanations, operator + is implemented as a member function. In the example above operator + is a free function, not a member of any class. The problem with making it a member function is that it must have an object that it can be applied to. For example, consider operator + as a member function: BigInt BigInt::operator +(const BigInt & rhs) // postcondition: returns (*this) + rhs { BigInt copy(*this); // code here to add rhs to copy, and return result } The copy of *this is required since evaluating a + b should not result in changing the value of a. Note that a + b is the same as a.operator +(b) when operator + is a member function. The real drawback here is that the following statements are legal when operator + is a member function: BigInt a = Factorial(50); // a large number BigInt b = a + 1; // one more than a large number However, the following statements are not legal: BigInt a = Factorial(50); // a large number BigInt b = 1 + a; // one more than a large number

The expression a + 1 compiles and executes because (we're assuming) that there is a BigInt constructor that will create a BigInt from an int, i.e., the constructor below is implemented:

BigInt::BigInt(int num); // postcondition: *this has the value num

This constructor is used to create an anonymous BigInt variable for the int value 1. This anonymous variable is passed to the function operator +. However, the symmetric expression 1 + a cannot be evaluated if operator + is a member function because the translation to 1.operator +(a) is syntactic nonsense --- 1 is a literal, it cannot have a member function applied to it nor will C++ create an anonymous variable so that a member function can be applied.

The binary arithmetic operators are commutative. When they're overloaded they should behave as users expect them to. So for symmetry/commutativity, binary arithmetic operators cannot be member functions.

The alternative is to make operator + a friend function, then it has access to the private instance variables of the class for which it is overloaded. However, the approach outlined above where operator + is implemented in terms of operator += avoids declaring friend functions. Since friend status should be granted sparingly, and since clients of a class cannot grant friendship after the class declaration is fixed, the approach outlined here should be used.

Consequences

The approach here uses a local variable that is a copy of one of the parameters. A copy is also made when the value is returned from the function. Since the function must return by-value, the copy on return cannot be avoided. Since we don't want a + b to have the side effect of altering the value of a a copy of a cannot be avoided. Furthermore, compiler optimization should be able to avoid the copy made for returning in most situations.


Arithmetic Assignment Operators

Again we'll use operator += for a class BigInt as an examplar of the syntax and semantics for overloading arithmetic assignment operators. const BigInt& BigInt::operator += (const BigInt & rhs) // postcondition: rhs has been added to *this, // *this returned Using this prototype the code below compiles: BigInt a = Factorial(25); BigInt b = Factorial(30); a += b; BigInt c = (b += b); Note that operator += returns a value (a constant reference) that is assigned to c. This isn't typical, but it's legal C++ for the built-in arithmetic operators, so it should be legal for overloaded arithmetic operators.

Overloaded operators should have the same semantics as their built-in counterparts.

Return Value

A reference is returned since there is no reason to make a copy. A const reference is returned so that the returned value is not an lvalue, i.e., so that it cannot be assigned to: BigInt a = Factorial(25); BigInt b = Factorial(30); (a += b) = b; // this is NOT legal C++ !!! The expression (a += b) is not an lvalue since the value returned is a const BigInt&; the const modifier is the essential piece of preventing the return value from being an lvalue.

The expression returned from an overloaded arithmetic operator should be *this, the value of the object being operated on:

const BigInt& BigInt::operator += (const BigInt & rhs) // postcondition: rhs has been added to *this, // *this returned { // code here return *this; }

Aliasing

In one of the examples above the expression b += b is used. In this case the parameter rhs will be an alias for the object on which operator += is invoked. This can cause problems in some situations since the value of rhs may change during the computation of intermediate results (well rhs doesn't change, it's const, but it's an alias for *this whose instance variables may be changing as the function operator += executes).

When aliasing could cause a problem this needs to checked as a special case:

if (this == &rhs) // special case In some cases where int values are being converted to the class for which operators are overloaded it may be possible to use the idea below (this is from the class BigInt). if (this == &rhs) // to add self, multiply by 2 { *this *= 2; return *this; } This will not always be possible because operator *= will not always be overloaded for int values.

Special Cases

Sometimes, often for efficiency (but make it right before making it fast), arithmetic operators are overloaded more than once for a given class. For example, the class BigInt has the following overloaded member functions and free functions. // member functions const BigInt & operator *= (const BigInt &); const BigInt & operator *= (int num); // free functions BigInt operator *(const BigInt & lhs, const BigInt & rhs); BigInt operator *(const BigInt & lhs, int num); BigInt operator *(int num, const BigInt & rhs); Here it's possible to evaluate b * 5 for a BigInt b variable, without converting the 5 to an anonymous variable. This may be done for efficiency or because the specialized versions of operator += and operator + are used in implementing the non-specialized versions. Note that for symmetry operator + is overloaded twice for adding BigInt and int values.

Relational Operators

Implementing the boolean relational operators <, >, <=, >=, ==, and != requires a technique similar to how binary arithmetic operators are implemented. This is because we want to be able to write the code below (all three comparison expressions involving <): BigInt x; // code giving x a value if (x < y) // do something if (x < 128) // do something if (1024 < x) // do something For reasons similar to those outlined above, the creation of anonymous variables for either left- or right-hand sides of a relational expression (e.g., involving < or ==) requires that these operators not be member functions.

Although relational operators can be implemented as friend functions, there is an easy method for implementing them that is similar to the method using arithmetic assignment operators such as += to implement the corresponding relational operator, in this case +, that avoids declaring any friend functions.

For example, consider a class Date for representing calendar dates, e.g., January 23, 1999. Determining if two dates are equal, or if one comes before another, can be done simply if == and < (and the other relational operators) are overloaded for Dateobjects. The approach I use is illustrated by the partial declaration of the Date class shown that follows:

class Date { public: // constructors and other member functions elided // operators: arithmetic and relational (partial list) const Date& operator +=(long dx); // add dx, e.g., jan 1 + 31 = feb 1 const Date& operator -=(long dx); // subtract dx, e.g., jan 1 - 1 = dec 31 bool equal(const Date & rhs) const; // returns true iff == rhs bool less(const Date & rhs) const; // returns true iff < rhs private: }; Here the functions equal and less are used to determine if one Date is equal to or less than another, respectively. These functions are implemented in order to facilitate overloading the relational operators although these functions can be useful in debuggers. The code below shows equal in use. Date a(1,1,1998), b(12,31, 1997); if (a.equal(b+1)) // just checking

Using functions equal and less is how relational functions are used in Java, so using this approach in C++ also eases a transition to Java. Once the functions are implemented, implementing the overloaded relational operators is straightforward. Again, fo the class Date we have:

bool operator == (const Date & lhs, const Date & rhs) { return lhs.equal(rhs); } bool operator != (const Date & lhs, const Date & rhs) { return ! (lhs == rhs); } bool operator < (const Date & lhs, const Date & rhs) { return lhs.less(rhs); } bool operator > (const Date & lhs, const Date & rhs) { return rhs < lhs; } bool operator <= (const Date & lhs, const Date & rhs) { return lhs == rhs || lhs < rhs; } bool operator >= (const Date & lhs, const Date & rhs) { return rhs <= lhs; } In these examples only == and < use the member functions equal and less directly, the other overloaded operators are implemented in terms of == and <. However, it's clearly possible to use equal and less only for implementing all the overloaded operators.

When using the STL (Standard Template Library) the header file <function> is typically included. Templated function declarations in this file implement all relational operators in terms of < and == so typically only these operators are overloaded for classes that are used in environments in which STL is available. For example, part of the SGI implementation of the header file function.h is shown below:

template <class T> inline bool operator!=(const T& x, const T& y) { return !(x == y); } template <class T> inline bool operator>(const T& x, const T& y) { return y < x; }

I/O Operators

Implementing the extraction and insertion operators >> and << requires access to the private data members and the stream to access.

The insertion operator <<

ostream & operator << (ostream & out, const Object & opnd) // Free fucntion { opnd.Print(out); return out; } The above free fucntion cannot access the private data members, so instead, it calls a public member function, passing on the object to output and the stream to send it to. The function needs to return the stream for chaining (e.g. cout << x << y << z;).

void Print(ostream & out) const // Public member function { out << privateDataMember ; } The above public member function can directly access the private data member(s) and send it to the passed output stream. This function can format the private data members.

The extraction operator >>

istream & operator >> (istream & in, Object & opnd) // Free fucntion { opnd.Input(in); return in; } Once again, the above free fucntion cannot access the private data members, so instead, it calls a public member function, passing on the object to output and the stream to send it to. The function needs to return the stream for chaining (e.g. cin >> x >> y >> z;).

void Input(istream & in) // Public member function { in >> privateDataMember; } The above public member function can directly access the private data member(s) and get data from the input stream and place into the correct private data members.

I/O Operators written by Greg Volger


Owen L. Astrachan
Last modified: Mon Jan 26 13:28:15 EST 1998