Tuesday, January 18, 2011

Overloading the arithmetic operators

Some of the most commonly used operators in C++ are the arithmetic operators — that is, the plus operator (+), minus operator (-), multiplication operator (*), and division operator (/). Note that all of the arithmetic operators are binary operators — meaning they take two operands — one on each side of the operator. All four of these operators are overloaded in the exact same way.
Overloading operators using friend functions
When the operator does not modify its operands, the best way to overload the operator is via friend function. None of the arithmetic operators modify their operands (they just produce and return a result), so we will utilize the friend function overloaded operator method here.
The following example shows how to overload operator plus (+) in order to add two “Cents” objects together:
01class Cents
02{
03private:
04    int m_nCents;
05
06public:
07    Cents(int nCents) { m_nCents = nCents; }
08
09    // Add Cents + Cents
10    friend Cents operator+(const Cents &c1, const Cents &c2);
11
12    int GetCents() { return m_nCents; }
13};
14
15// note: this function is not a member function!
16Cents operator+(const Cents &c1, const Cents &c2)
17{
18    // use the Cents constructor and operator+(int, int)
19    return Cents(c1.m_nCents + c2.m_nCents);
20}
21
22int main()
23{
24    Cents cCents1(6);
25    Cents cCents2(8);
26    Cents cCentsSum = cCents1 + cCents2;
27    std::cout << "I have " << cCentsSum .GetCents() << " cents." << std::endl;
28
29    return 0;
30}
This produces the result:
I have 14 cents.
Overloading the plus operator (+) is as simple as declaring a function named operator+, giving it two parameters of the type of the operands we want to add, picking an appropriate return type, and then writing the function.
In the case of our Cents object, implementing our operator+() function is very simple. First, the parameter types: in this version of operator+, we are going to add two Cents objects together, so our function will take two objects of type Cents. Second, the return type: our operator+ is going to return a result of type Cents, so that’s our return type.
Finally, implementation: to add two Cents objects together, we really need to add the m_nCents member from each Cents object. Because our overloaded operator+() function is a friend of the class, we can access the m_nCents member of our parameters directly. Also, because m_nCents is an integer, and C++ knows how to add integers together using the built-in version of the plus operator that works with integer operands, we can simply use the + operator to do the adding.
Overloading the subtraction operator (-) is simple as well:
01class Cents
02{
03private:
04    int m_nCents;
05
06public:
07    Cents(int nCents) { m_nCents = nCents; }
08
09    // overload Cents + Cents
10    friend Cents operator+(const Cents &c1, const Cents &c2);
11
12    // overload Cents - Cents
13    friend Cents operator-(const Cents &c1, const Cents &c2);
14
15    int GetCents() { return m_nCents; }
16};
17
18// note: this function is not a member function!
19Cents operator+(const Cents &c1, const Cents &c2)
20{
21    // use the Cents constructor and operator+(int, int)
22    return Cents(c1.m_nCents + c2.m_nCents);
23}
24
25// note: this function is not a member function!
26Cents operator-(const Cents &c1, const Cents &c2)
27{
28    // use the Cents constructor and operator-(int, int)
29    return Cents(c1.m_nCents - c2.m_nCents);
30}
Overloading the multiplication operator (*) and division operator (/) are as easy as defining functions for operator* and operator/.
Overloading operators for operands of different types
Often it is the case that you want your overloaded operators to work with operands that are different types. For example, if we have Cents(4), we may want to add the integer 6 to this to produce the result Cents(10).
When C++ evaluates the expression x + y, x becomes the first parameter, and y becomes the second parameter. When x and y have the same type, it does not matter if you add x + y or y + x — either way, the same version of operator+ gets called. However, when the operands have different types, x + y is not the same as y + x.
For example, Cents(4) + 6 would call operator+(Cents, int), and 6 + Cents(4) would call operator+(int, Cents). Consequently, whenever we overload binary operators for operands of different types, we actually need to write two functions — one for each case. Here is an example of that:
01class Cents
02{
03private:
04    int m_nCents;
05
06public:
07    Cents(int nCents) { m_nCents = nCents; }
08
09    // Overload cCents + int
10    friend Cents operator+(const Cents &cCents, int nCents);
11
12    // Overload int + cCents
13    friend Cents operator+(int nCents, const Cents &cCents);
14
15    int GetCents() { return m_nCents; }
16};
17
18// note: this function is not a member function!
19Cents operator+(const Cents &cCents, int nCents)
20{
21    return Cents(cCents.m_nCents + nCents);
22}
23
24// note: this function is not a member function!
25Cents operator+(int nCents, const Cents &cCents)
26{
27    return Cents(cCents.m_nCents + nCents);
28}
29
30int main()
31{
32    Cents c1 = Cents(4) + 6;
33    Cents c2 = 6 + Cents(4);
34    std::cout << "I have " << c1.GetCents() << " cents." << std::endl;
35    std::cout << "I have " << c2.GetCents() << " cents." << std::endl;
36
37    return 0;
38}
Note that both overloaded functions have the same implementation — that’s because they do the same thing, they just take their parameters in a different order.
Another example
Let’s take a look at another example:
01class MinMax
02{
03private:
04    int m_nMin; // The min value seen so far
05    int m_nMax; // The max value seen so far
06
07public:
08    MinMax(int nMin, int nMax)
09    {
10        m_nMin = nMin;
11        m_nMax = nMax;
12    }
13
14    int GetMin() { return m_nMin; }
15    int GetMax() { return m_nMax; }
16
17    friend MinMax operator+(const MinMax &cM1, const MinMax &cM2);
18    friend MinMax operator+(const MinMax &cM, int nValue);
19    friend MinMax operator+(int nValue, const MinMax &cM);
20};
21
22MinMax operator+(const MinMax &cM1, const MinMax &cM2)
23{
24    // Get the minimum value seen in cM1 and cM2
25    int nMin = cM1.m_nMin < cM2.m_nMin ? cM1.m_nMin : cM2.m_nMin;
26
27    // Get the maximum value seen in cM1 and cM2
28    int nMax = cM1.m_nMax > cM2.m_nMax ? cM1.m_nMax : cM2.m_nMax;
29
30    return MinMax(nMin, nMax);
31}
32
33MinMax operator+(const MinMax &cM, int nValue)
34{
35    // Get the minimum value seen in cM and nValue
36    int nMin = cM.m_nMin < nValue ? cM.m_nMin : nValue;
37
38    // Get the maximum value seen in cM and nValue
39    int nMax = cM.m_nMax > nValue ? cM.m_nMax : nValue;
40
41    return MinMax(nMin, nMax);
42}
43
44MinMax operator+(int nValue, const MinMax &cM)
45{
46    // call operator+(MinMax, nValue)
47    return (cM + nValue);
48}
49
50int main()
51{
52    MinMax cM1(10, 15);
53    MinMax cM2(8, 11);
54    MinMax cM3(3, 12);
55
56    MinMax cMFinal = cM1 + cM2 + 5 + 8 + cM3 + 16;
57
58    std::cout << "Result: (" << cMFinal.GetMin() << ", " <<
59        cMFinal.GetMax() << ")" << std::endl;
60
61    return 0;
62}
The MinMax class keeps track of the minimum and maximum values that it has seen so far. We have overloaded the + operator 3 times, so that we can add two MinMax objects together, or add integers to MinMax objects.
This example produces the result:
Result: (3, 16)
which you will note is the minimum and maximum values that we added to cMFinal.
One other interesting thing to note is that we defined operator+(int, MinMax) by calling operator+(MinMax, int). This is slightly less efficient than implementing it directly (due to the extra function call), but keeps our code shorter and easier to maintain (because it reduces duplicate code). It is often possible to define overloaded operators by calling other overloaded operators — when possible, do so!

No comments: