Section 3 - Operator Overloading
5A.3.1 Defining Operator Functions
Let's start with a definition of an operator function to be one whose name is
operatorX( ... )
where X is any valid C++ operator.
We will refine this definition, but as it stands, we can start forming examples. The plus sign, +, is a C++ operator, so that means we can define a function whose name is operator+. Next, we restrict the number of parameters to be either one or two -- one, if the original C++ operator is unary (^, !, ... ) and two, if it is binary (+, / *, ...).
Finally, we stipulate that at least one of the parameters be an object or object reference of some user-defined class.
So, our operator+() function could be defined as:
Dog operator+(Dog dog1, Dog dog2)
{
Dog tempDog;
tempDog.weight = (dog1.weight + dog2.weight) / 2; // avg weight
tempDog.name = dog1.name + " " + dog2.name; // concat names
// or whatever else makes sense ...
return tempDog;
}
where Dog is assumed to be a class. This means we have a new function that takes two Dogs, returns a Dog , and whose name is operator+(). I don't care what the function does right now. I place a couple of random lines in the definition just to show you we could have it do anything.
To call it we could (but never would) use an ordinary function call:
Dog fido, watson, lucy;
fido = operator+(watson, lucy);
This is perfectly legal and in keeping with the modest definition of an operator function. Problem is, who needs a function with such a weird name.
The punch line is: You could call the above function using the more intuitive
fido = watson + lucy;
That is, the C++ compiler figures out the + between watson and lucy is neither numeric addition nor string concatenation, but a call to our function operator+(), and it knows to pass watson and lucy as arguments to the function.
Suddenly, we can make operators do different things than C++ would have them do. The main catch is that at least one of the two operands be a programmer-defined object.
Defining an operator function is called operator overloading because we have an overloaded meaning to the ordinary C++ operators.
The above example showed the hypothetical definition of a non-member operator function because it was not meant to be defined inside any class. Sometimes such an operator function has to be declared to be a friend of the class, particularly if it needed to access any private data of the class, as is often the case. However, we can also make an operator function a member, and if we do so, it would take one fewer parameters (omit the first parameter). What happens to the first parameter? Guess.
If you guessed that the first parameter does not have to be passed because the operator function uses the this object that called the function as its first parameter, you were correct. In the case above, if the + operator were a member operator, it would look something like this:
Dog Dog::operator+(Dog dog2) { //... }
5A.3.2 Remarks About Operators
Here are some important facts about operator overloading:
- You can only overload operators that are already defined to be C++ operators. You can't make up your own. So += and /= are okay, but @= isn't.
- The precedence of operators is preserved. You can't make + have higher precedence than *, even though it has a completely different action.
- The [] and () operators are like any binary operator in concept, however their calling syntax is special so that they may be used as is the custom. Thus rather than saying:
a = b[]c;
as we would if it were a + or % operator) we call the function with
a = b[c];
The compiler knows that b and c are the parameters to send to the operator function (or, if it is a member function then the object b calls it and c is a parameter). This and other things I say about brackets operators apply equally to parens, ().
- Normally we overload the brackets operator, [], in classes that emulate arrays or other sequential data structures, and we let c be an int or long -- the virtual index into the array. Furthermore, since we want to allow assignments like this:
b[c] = a;
we define the operator[] function with a return type of CLASS &, where CLASS is the data type of b. You will see an example of this in a couple weeks.
- If an operator is a non-member function then at least one parameter must be an object (or object reference). If it is a member function, then this requirement is already satisfied implicitly by the this calling object, leaving the remaining parameter (if any) free to be whatever it wants to be. (Even parameters have wants and needs, you know.)
- The choice between member or non-member is somewhat arbitrary. If the operator is binary and it is commutative, then it makes more sense for it to be a non-member, to avoid asymmetry. If it is a unary or non-commutative operation, then it is often made a member function.
- There are a few operators which are not overload-able (see your text).
- The assignment operator, =, must be a member function. Also, while not mandatory, it should usually have the same sort of action as a copy constructor would, the difference being the place from where the two are called (i.e., initialization vs. executable assignment). Normally if you need a copy constructor, you also need to overload =.
This last item is important and something that you should, at your level of expertise now, be able to really digest, understand and experiment with. Go back to the lesson on copy constructors and deep copies and reread it, replacing the original idea of initializing an object without a copy constructor with the idea of assigning an object without an assignment operator. You should be able to see the exact parallels and understand that the insides of the assignment operator overload should do, essentially, what the copy constructor did. This, however, is only critical if you have deep data in your object. If there is only shallow data, then neither a copy constructor nor an assignment operator overload is needed.