+
, -
, *
, /
, %
)These operators all have the following things in common:
5 + 2
does not change either 5
or 2
. Therefore, an operand that is an object should be passed as a reference to a const
object. If an operand is a simple built-in data type like int
or float
, it should be passed by value instead. If an overloaded arithmetic operator is implemented as a member function, the member function should be declared to be const
as well.5 + 2
should return 7
.The arithmetic operators may always be overloaded as standalone functions. For an operator that will take two objects of your new class as operands, the skeleton of the function definition should always look like this:
ClassName operator symbol(const ClassName& lhs, const ClassName& rhs) { ClassName result; // Declare a temporary object to hold the result // Perform arithmetic with lhs and rhs, storing the result in result // Return the temporary result return result; }
In the code skeleton above, items in red will need to be changed by the programmer - plug in the name of your class and the operator symbol you want to overload. Items in black are standard and generally don't change no matter what arithmetic operator is being overloaded or what class the operator is overloaded for.
To see how this works in practice, let's look at a specific example.
Example 1: The *
operator overloaded for the Rational
class as a standalone function
In this example, the *
operator will be used to multiply two Rational
objects and get a Rational
object as a result. If we take the code above and plug in Rational
for ClassName
and
*
for symbol
, we'll have the skeleton of the overloaded operator function:
Rational operator*(const Rational& lhs, const Rational& rhs) { Rational result; // Declare a temporary object to hold the result. // Perform arithmetic with lhs and rhs, storing the result in result. // Return the temporary result. return result; }
But how do we write the logic to actually multiply the two rational numbers? The answer has less to do with C++ than with mathematics, which defines the product of two rational numbers as:
a c ac ― • ― = ―― if b ≠ 0, d ≠ 0 b d bd
So, the numerator member of the result should be set to the product of the numerator members of the two operands. The denominator member of the result should be set to the product of the denominator members of the two operands.
As the final step of the arithmetic we will reduce the rational number to its lowest terms. This is standard mathematical practice and it will make implementing some of the other overloaded operators much easier.
Because this function has not been defined as part of the Rational
class, it has no direct access to the class's private
data members. We will need to use accessor method calls to obtain the values or change the values of the numerator and denominator. We end up with code that looks like this:
Rational operator*(const Rational& lhs, const Rational& rhs)
{
Rational result; // Declare a temporary object to hold the result.
// Multiply the numerators and denominators of the two rational numbers,
// storing the result in result.
result.setNumerator(lhs.getNumerator() * rhs.getNumerator());
result.setDenominator(lhs.getDenominator() * rhs.getDenominator());
// Reduce the result to lowest terms:
// First, find the greatest common divisor for the numerator and
// denominator of the result.
int gcd = find_gcd(result.getNumerator(), result.getDenominator());
// Then, divide the numerator and denominator by the greatest common divisor.
result.setNumerator(result.getNumerator() / gcd);
result.setDenominator(result.getDenominator() / gcd);
// Return the temporary result.
return result;
}
Although the code above will work, notice that twelve (!) accessor method calls were required to multiply two Rational
objects. If we could eliminate the need for these method calls, it would simplify the code for the function and speed up its execution considerably.
By designating an overloaded operator function to be a friend
of the Rational
class, we can grant the function direct access to the Rational
class’s private
data members, eliminating the need to call accessor methods.
Example 2: The *
operator overloaded as a standalone friend
function of the Rational
class
To designate our overloaded operator function as a friend
of the Rational
class, we need to include the function's prototype, preceded by the keyword friend
, anywhere in the declaration of the Rational
class:
class Rational
{
friend Rational operator*(const Rational&, const Rational&);
private:
int numerator,
denominator;
public:
.
.
.
};
The function definition can then be coded like this:
Rational operator*(const Rational& lhs, const Rational& rhs)
{
Rational result; // Declare a temporary object to hold the result.
// Multiply the numerators and denominators of the two rational numbers,
// storing the result in result.
result.numerator = lhs.numerator * rhs.numerator;
result.denominator = lhs.denominator * rhs.denominator;
// Reduce the result to lowest terms:
// First, find the greatest common divisor for the numerator and
// denominator of the result.
int gcd = find_gcd(result.numerator, result.denominator);
// Then, divide the numerator and denominator by the greatest common divisor.
result.numerator /= gcd;
result.denominator /= gcd;
// Return the temporary result.
return result;
}
This version of the function is a definite improvement over the version in Example 1.
An overloaded operator implemented as a standalone function should normally be made a friend
of the class.
Once we've overloaded this operator as a standalone function, we can use it to multiply two objects of the Rational
class:
Rational r1(3, 5); // Create r1 and set it = 3/5.
Rational r2(2, 3); // Create r2 and set it = 2/3.
Rational r3;
r3 = r1 * r2; // Generates the function call r3 = operator*(r1, r2);
// Sets r3 = 6/15, which is then reduced to 2/5.
Of course, another way of giving a function access to a class's private
data is to make the function a member function of the class.
The left operand will be passed implicitly and can be accessed using the this
pointer. This means that a member function to overload a binary arithmetic operator will take one argument rather than the usual two.
This also means that the left operand must be an object of our new class in order to overload the operator as a member function of our class. We can code an overloaded operator member function that takes two Rational
objects as operands, or an overloaded operator member function that takes a Rational
object as the left operand and an int
as the right operand. If we want an overloaded operator function that takes an int
as the left operand and a Rational
object as the right operand, that function can not be implemented as a member function of the Rational
class. It will need to be implemented as a standalone friend
function instead.
The member function will not change the data members of the object that called it (the left operand), so it should be declared const
.
Here's a skeleton of the code for overloading an arithmetic operator as a member function:
ClassName ClassName::operator symbol(const ClassName& rhs) const { ClassName result; // Declare a temporary object to hold the result. // Perform arithmetic with the object pointed to by this and rhs, storing the result in result. // Return the temporary result return result; }
Now let's look at a specific example.
Example 3: The *
operator overloaded as a member function of the Rational
class
Rational Rational::operator*(const Rational& rhs) const
{
Rational result; // Declare a temporary object to hold the result.
// Multiply the numerators and denominators of the two rational numbers,
// storing the result in result.
result.numerator = this->numerator * rhs.numerator;
result.denominator = this->denominator * rhs.denominator;
// Reduce the result to lowest terms:
// First, find the greatest common divisor for the numerator and
// denominator of the result.
int gcd = find_gcd(result.numerator, result.denominator);
// Then, divide the numerator and denominator by the greatest common divisor.
result.numerator /= gcd;
result.denominator /= gcd;
// Return the temporary result.
return result;
}
Notice that the code here is extremely similar to the code for Example 2. There's also no difference in how the overloaded operator is used, although the actual call generated by the compiler is different:
Rational r1(3, 5); // Create r1 and set it = 3/5.
Rational r2(2, 3); // Create r2 and set it = 2/3.
Rational r3;
r3 = r1 * r2; // Generates the member function call r3 = r1.operator*(r2);
// Sets r3 = 6/15, which is then reduced to 2/5.
It's important to remember that there may be more than one valid way to write the code for an overloaded operator function or member function. For example, there's really no need to explicitly code this->
to access the numerator and denominator of the left operand:
Rational Rational::operator*(const Rational& rhs) const
{
Rational result; // Declare a temporary object to hold the result
// Multiply the numerators and denominators of the two rational numbers,
// storing the result in result.
result.numerator = numerator * rhs.numerator;
result.denominator = denominator * rhs.denominator;
// Reduce the result to lowest terms:
// First, find the greatest common divisor for the numerator and
// denominator of the result
int gcd = find_gcd(result.numerator, result.denominator);
// Then, divide the numerator and denominator by the greatest common divisor
result.numerator /= gcd;
result.denominator /= gcd;
// Return the temporary result
return result;
}
Another option is to directly assign one of the operands to the result
object and then do the math with that result
object and the other operand. That can sometimes allow us write slightly shorter code:
Rational Rational::operator*(const Rational& rhs) const
{
Rational result = *this; // Declare a temporary object to hold the result
// and initialize it with the left operand.
// Multiply the numerators and denominators of the two rational numbers,
// storing the result in result.
result.numerator *= rhs.numerator;
result.denominator *= rhs.denominator;
// Reduce the result to lowest terms:
// First, find the greatest common divisor for the numerator and
// denominator of the result
int gcd = find_gcd(result.numerator, result.denominator);
// Then, divide the numerator and denominator by the greatest common divisor.
result.numerator /= gcd;
result.denominator /= gcd;
// Return the temporary result.
return result;
}
Depending on what you need to do in your overloaded operator, you might choose to create an empty "default" result, initialize the result with the left operand, or initialize the result with the right operand.
As mentioned above, you can't always overload an arithmetic operator as a member function - it depends on the data type of the left operand. For example, let's say I wanted to be able to use the *
operator to find the product of a Rational
object and an integer.
If the integer is the right operand, I can overload the operator as a method, because the method call that will be generated by the compiler is valid:
Rational r1(3, 5); // Create r1 and set it = 3/5.
Rational r2;
r2 = r1 * 5; // Generates the method call r3 = r1.operator*(5);
// Sets r2 = 15/5, which is then reduced to 3/1.
The code for this method would just be a slight variation of the code to multiply two Rational
objects:
Rational Rational::operator*(int rhs) const
{
Rational result; // Declare a temporary object to hold the result.
// Multiply the numerator of the left operand by the integer, storing the
// result in result.
result.numerator = numerator * rhs;
// Reduce the result to lowest terms:
// First, find the greatest common divisor for the numerator and
// denominator of the result.
int gcd = find_gcd(result.numerator, result.denominator);
// Then, divide the numerator and denominator by the greatest common divisor.
result.numerator /= gcd;
result.denominator /= gcd;
// Return the temporary result.
return result;
}
However, if the integer is to be the left operand, overloading the operator as a member function would produce an produces an illegal function call:
Rational r1(3, 5); // Create r1 and set it = 3/5
Rational r2;
r2 = 5 * r1; // Generates the function call r3 = 5.operator*(r1);
// NOT A VALID MEMBER FUNCTION CALL, since 5 is not an
// object of the Rational class.
Overloading the operator as a standalone function does produces a valid function call though:
Rational r1(3, 5); // Create r1 and set it = 3/5.
Rational r2;
r2 = 5 * r1; // Generates the function call r3 = operator*(5, r1);
Code for the overloaded operator as a standalone function might look like this (assuming that the function is made a friend
of the Rational
class:
Rational operator*(int lhs, const Rational& rhs)
{
Rational result; // Declare a temporary object to hold the result.
// Multiply the numerator of the right operand by the integer, storing the
// result in result.
result.numerator = lhs * rhs.numerator;
// Reduce the result to lowest terms:
// First, find the greatest common divisor for the numerator and
// denominator of the result.
int gcd = find_gcd(result.numerator, result.denominator);
// Then, divide the numerator and denominator by the greatest common divisor.
result.numerator /= gcd;
result.denominator /= gcd;
// Return the temporary result.
return result;
}
Remember: you can't make an overloaded operator function a method if its left operand is not an object or is an object of a different class.