Studying an example is the easiest way of approaching overloaded operators. To demonstrate operators, we revisit the fraction example first introduced in the Classes and Objects chapter. Although the original presentation of the fraction class described how the constructor operates, we'll use fractions throughout this chapter to illustrate different operators and options, so it's appropriate to review how fraction objects are created.
fraction(int n = 0, int d = 1); |
fraction f1; // i fraction f2(5); // ii fraction f3(2, 3); // iii |
(a) | (b) |
Whenever class designers choose to overload an operator, they are free to choose what an operator means in the context of their class. For example, the + operator may naturally represent a concatenation operation in a string class. For classes like "fraction" that represent some kind of numerical data, the meaning of the arithmetic operators (+, -, *, and /) is generally quite clear. Together, fraction.h and fraction.cpp form a supplier that provides basic fraction operations, implemented as overloaded operators, that any client code may use.
fraction.h |
class fraction { private: int numerator; int denominator; public: fraction(int n = 0, int d = 1) : numerator(n), denominator(d); fraction operator+(fraction f); fraction operator-(fraction f); fraction operator*(fraction f); fraction operator/(fraction f); }; |
---|---|
fraction.cpp |
#include "fraction.h" fraction fraction::operator+(fraction f) { int n = numerator * f.denominator + denominator * f.numerator; int d = denominator * f.denominator; return fraction(n, d); } fraction fraction::operator-(fraction f) { int n = numerator * f.denominator - denominator * f.numerator; int d = denominator * f.denominator; return fraction(n, d); } fraction fraction::operator*(fraction f) { int n = numerator * f.numerator; int d = denominator * f.denominator; return fraction(n, d); } fraction fraction::operator/(fraction f) { int n = numerator * f.denominator; int d = denominator * f.numerator; return fraction(n, d); } |
The above example illustrates that the function prototypes and definitions for overloaded operators are similar to "regular" member functions; they just have different names. But overloaded operators must differ from regular functions somehow or we would just continue to use function names like add
and sub
. The difference is how we use the operators, that is, how we call the operator functions. The following driver, a client using the fraction supplier files, illustrates how programs call all the overloaded arithmetic operators.
There it is, there's the big payoff! We can use operators with instances of our classes just like we can use operators with the fundamental data types like int and double. Was it worth it? That's pretty much a matter of opinion. Some computer scientists don't like overloaded operators. The white paper that initially described the purpose and goals of the Java Programming Language makes it clear that the Java designers did not like overloaded operators and deliberately did not borrow them from C++. I personally like overloaded operators, but that preference aside, C++ programmers need to understand them so that they can read and understand programs written by others.
Our next challenge is to understand the relationship between function arguments and operator operands. Operands, are
the values on which operators work or operate - they appear next to the operator that is operating on them (in the case of binary operators, they appear both to the left and to the right of the operator). Arguments are the data passed from function calls into functions - they are the elements that appear between the opening and closing parentheses that are a part of the function call. Finally, member function arguments may be either implicit or explicit where the implicit argument is the object that calls and is bound to the function through the this
pointer, and explicit arguments appear between the parentheses following the function name. All of these concepts come together in overloaded operators implemented with member functions.
The functions that implement overloaded operators may be called in two ways. The first way is based on an operator notation and is common, convenient, and familiar. The second way reflects that overloaded operators are just functions. This notation is uncommon and typically only used to help us understand the transition from "regular" functions and their arguments to overloaded operators and their operands.
fraction x = a + b; |
fraction x = a.operator+(b); |
(a) | (b) |
Binary Operator Member Function | Unary Operator Member Function |
---|---|
(a) | (b) |
this
pointer), and there are no explicit arguments.We'll use two working definitions to help us talk about overloaded operators: symmetric and asymmetric operators. Our definitions are similar to the commutative property defined on real numbers, but they are not the same.
|
|
Numerically, adding an integer to a fraction is a valid commutative operation. Given the fraction 1/2, the operation 1/2 +1 = 1½ = 3/2. Although numerically valid, the operation doesn't match the overloaded operator fraction operator+(fraction f);
described above. As currently implemented, the fraction addition operator is symmetric: both operands must be fraction objects. Functions, including those that implement overloaded operators, can be overloaded as long as they obey the requirement that the parameter list of each overloaded version is unique. So, we can solve this problem by adding a second overloaded addition function that has an integer parameter, which is equivalent to adding a second addition operator that has an integer as the right-hand operand:
fraction fraction::operator+(int i) { int n = numerator + denominator * i; return fraction(n, denominator); } |
a + 5
is mathematically valid, and when it is viewed as a function call, it clearly matches the signature of the overloaded operator+
function. Furthermore, as illustrated, the left hand operand, a, becomes the implicit or this
object and the right hand operand, 5, becomes the explicit argument passed inside the parentheses. operator+(int) demonstrates an asymmetric operator.