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
inline bool operator!=(const T& x, const T& y) {
return !(x == y);
}
template
inline bool operator>(const T& x, const T& y) {
return y < x;
}
Owen L. Astrachan
Last modified: Mon Jan 26 13:28:15 EST 1998