Chapter XXVIII
Chapter XXVIII
Chapter XXVIII
28.1 Introduction
Polymorphism Definition
When you think back on the last two chapters you realize that some overloaded
aspects were already used. The focus of the last two chapters was on member
functions in general and constructors and destructors specifically. In the process
of detailing constructor features we did look at overloaded constructors. Well,
such is the normal course of events in computer science. Try, as you can to
follow the beautiful elegance of a mathematical sequence of topics, and you will
get bitten. Computer science is too interrelated. However, that is fine because
these chapters are all titled Focus on Object Oriented Programming followed by
the particular topic of the focus.
The OOP topic of this chapter is overloaded operators. The chapter is not called
polymorphism because overloaded operators are a subset of the larger topic of
polymorphism. Overloaded operators bring exciting programming power to the
C++ language and is one major reason for the popularity of C++.
In the first few computer science chapters with C++, you used overloaded
operators without knowing or caring. One common example is the use of the plus
( + ) operator with the int type to perform arithmetic addition, such as the
statement Sum = Number1 + Number2. The same plus operator with totally
different results occurs with string concatenation in a program statement, such as
FullName = FirstName + LastName.
Perhaps the best way to demonstrate the difference is with these two plus operator
examples, which look almost identical but have totally different results.
Just about any language has such features that various operators are used for
different procedures, and the procedure selected depends on the context of the
operator. A plus operator placed between two integer types performs arithmetic
addition, and the same plus operator placed between two strings performs string
concatenation.
MatrixMultiplication(M1,M2,M3);
M3 = M1 * M2;
This should make a lot of sense. Earlier I mentioned that it is possible to have a
statement like M3 = M1 * M2; multiply two matrices by overloading the asterisk
operator. Now this only works because M1, M2 and M3 are objects of a special
class that declares a special multiply operator.
How about putting this all in simple English? Well here it goes. Create all the
overloaded operators you desire. Explain with proper C++ syntax how these
operators are supposed to behave inside some class. Whenever you use your nifty
operator make sure that it is done with objects of the class that you declared.
This entire chapter will use the same basic class to explain a variety of overloaded
operators. It is the PiggyBank class, abbreviated to Piggy. This is a very simple
Program PROG2801.CPP shows the fundamental Piggy class. It has one private
Savings attribute to store the total amount saved. There is a default constructor
that initializes Savings to $10.00, and a ShowSavings member function to display
the total amount saved in the piggybank.
// PROG2801.CPP
// This program declares the minimum Piggy class.
// The Piggy(bank) class will be used throughout the
// chapter to demonstrate overloaded operator concepts.
#include <iostream.h>
#include <conio.h>
class Piggy
{
private:
float Savings;
public:
Piggy()
{ Savings = 10; }
void ShowSavings() const
{ cout << Savings << " dollars saved" << endl; }
};
void main()
{
clrscr();
Piggy Tom;
Tom.ShowSavings();
getch();
}
PROG2801.CPP OUTPUT
10 dollars saved
The Piggy class lacks any input ability to alter the amount that is saved. The
constructor has no parameters, and Savings is private so you cannot do anything
to alter the value of $10.00 in the piggybank. This shortcoming is remedied in the
// PROG2802.CPP
// This program adds an Increment function to the Piggy class.
// Increment increases the savings by one dollar.
#include <iostream.h>
#include <conio.h>
class Piggy
{
private:
float Savings;
public:
Piggy()
{ Savings = 10; }
void ShowSavings() const
{ cout << Savings << " dollars saved" << endl; }
void Increment()
{ Savings++; }
};
void main()
{
clrscr();
Piggy Tom;
Tom.ShowSavings();
Tom.Increment();
Tom.ShowSavings();
getch();
}
PROG2802.CPP OUTPUT
10 dollars saved
11 dollars saved
Now think of the usual mathematical type operations. If you have a variable like
int X; do you call some special function to alter its value? No, what you do is
use some operators like X++ or X+=10. This very convenient operator capability
is what we aim to achieve with an object like Tom of our Piggy class. Rather
than using a statement like Tom.Increment(), we want to use ++Tom to increase
the savings of the piggybank or we want to use Tom++. These types of
operations is what will be added to Piggy in the rest of this chapter.
Program PROG2803.CPP, on the next page, will seem plenty weird. It is almost
a copy of the previous program, and it does have the same output. The previous
Increment function has now been replaced by an operator++ function. Aside
from the strange function identifier, everything is the same. Running the program
will verify that the syntax is correct and that the execution is identical to the
previous program. But since when are you allowed to use ++ characters in a
function identifier? Also - for those looking at a monitor - why is the identifier
operator, displayed in bold?
By now you should know that bold indicates a C++ reserved word. You are
observing the oddity that a function name is used that appears to be a reserved
word, and C++ is happy with this arrangement.
In just a moment some special use will be made of this operator keyword, but for
now the point is that a member function of an object, regardless of its name, can
be called using the standard dot notation of an object.
Note, in the main function that the statement Tom.operator++() uses the precise
same syntax as any other void public member function. The point is that the
operator functions that you will see in this chapter, are member functions, even if
they behave differently.
// PROG2803.CPP
// This program substitutes the Increment function with an
#include <iostream.h>
#include <conio.h>
class Piggy
{
private:
float Savings;
public:
Piggy()
{ Savings = 10; }
void ShowSavings() const
{ cout << Savings << " dollars saved" << endl; }
void operator++()
{ Savings++; }
};
void main()
{
clrscr();
Piggy Tom;
Tom.ShowSavings();
Tom.operator++();
Tom.ShowSavings();
getch();
}
PROG2803.CPP OUTPUT
10 dollars saved
11 dollars saved
The intention of overloaded operators is not to use the regular function syntax,
like the previous program, but use an abbreviated syntax that simulates the
manner in which operators are normally used. In this section we start by looking
at the overloaded unary operators.
Unary operators are a good starting point. A unary operator only has one operand
that makes life simpler at this stage. Our concern is how to substitute the earlier
Increment function with something that will make an object of the Piggy class
increase Savings with a statement like ++Tom. This task may seem rather
perplexing if it were not for the C++ operator reserved word. C++ has made this
overloading business rather simple. You want to give special meaning to the ++
operator? Fine, create a void function called operator++ and explain in the body
of the function what needs to be executed.
Program PROG2804.CPP uses the exact same Piggy class with the same
operator++ function as the previous program. The only difference is that the
normal dot.function notation style of member function calling is replaced with an
overloaded operator notation style. Only the main function is shown below
where you see the different function calling style.
// PROG2804.CPP
// This program substitutes the Increment function with an
// operator++ function, which accomplishes the same thing.
void main()
{
clrscr();
Piggy Tom;
Tom.ShowSavings();
++Tom; // before it was Tom.operator++();
Tom.ShowSavings();
getch();
}
PROG2804.CPP OUTPUT
10 dollars saved
11 dollars saved
The problem with postfix notation is illustrated in the next program. The same
operator++ function is called using both prefix and postfix notation. The
program does compile, and it does execute. However, C++ objects strongly with
a warning message after compiling.
// PROG2805.CPP
// This program calls the operator++ function using both
// prefix and postfix notation. The postfix notation
// causes a warning.
#include <iostream.h>
#include <conio.h>
class Piggy
{
private:
float Savings;
public:
Piggy()
{ Savings = 10; }
void ShowSavings() const
{ cout << Savings << " dollars saved" << endl; }
void operator++()
{ Savings++; }
};
void main()
{
clrscr();
Piggy Tom;
Tom.ShowSavings();
++Tom;
Tom.ShowSavings();
Tom++;
Tom.ShowSavings();
getch();
}
WARNING MESSAGE
10 dollars saved
11 dollars saved
12 dollars saved
The examples shown so far only incremented one data field, the Savings attribute.
It may seem natural that the ++ operator only increments one variable. There is
no such restriction in C++ and it is entirely possible to alter the values of multiple
attributes. Program PROG2806.CPP adds a Weight attribute to the Piggy class.
This new attribute stores the weight, in ounces, of the piggybank. This example
does not use the inline operator function, shown in earlier examples. This time
the operator function uses only the prototype inside the class, and the function
implementation is detailed later.
// PROG2806.CPP
// This program demonstrates that an operator function can alter
// more than one attribute. In this program the Piggy class also
// has a Weight attribute, which measures the weight of the
// Piggy(bank) in ounces.
#include <iostream.h>
#include <conio.h>
class Piggy
{
private:
float Savings;
int Weight;
public:
Piggy();
void ShowSavings() const;
void operator++();
};
void main()
{
clrscr();
Piggy Tom;
Tom.ShowSavings();
++Tom;
Tom.ShowSavings();
getch();
}
void Piggy::operator++()
{
Savings += 10;
Weight += 25;
}
PROG2806.CPP OUTPUT
10 dollars saved
Weight is 50 ounces
20 dollars saved
Weight is 75 ounces
This program example helps to explain several concepts that perhaps you took for
granted or had not understood. Notice that the operator++ not only increments
two different attributes with the same call, but the function also increments the
attributes with different amounts. You may have assumed that the ++ operator
would automatically increment by a value of one. Such beliefs are from using
integer variables like K++. In that case you are using the C++ provided
incrementer. When you declare an operator function, you have the choice to
increment any way that seems appropriate.
// PROG2807.CPP
// This program demonstrates two different operator functions.
#include <iostream.h>
#include <conio.h>
class Piggy
{
private:
float Savings;
public:
Piggy()
{ Savings = 25; }
void operator++()
{ Savings++; }
void operator--()
{ Savings--; }
void ShowSavings() const
{ cout << Savings << " dollars saved" << endl; }
};
void main()
{
clrscr();
Piggy Tom;
Tom.ShowSavings();
++Tom;
Tom.ShowSavings();
--Tom;
Tom.ShowSavings();
getch();
}
PROG2807.CPP OUTPUT
You may be able to sleep very soundly knowing that your operator++ is assumed
to be a prefix operation by C++. On the other hand, you might wake up at weird
hours irritated that postfix does not gets its foot in the door. Well sleep on
because you can use postfix. The method may seem quite peculiar because it is
done by providing an int parameter. This is interesting because the heading of a
function helps C++ identify the appropriate overloaded function. We now have a
situation where we are using overloaded functions to properly implement
overloaded operators.
This situation is a function with a special flag. C++ sees an operator++ heading
without any parameter and thinks that it a prefix operation. However if the same
function includes an int parameter, C++ knows that a postfix operation is
intended. This is a curious situation when a parameter does not pass a value nor
any type of reference, but exists for the purpose of identifying a function.
// PROG2808.CPP
// This program demonstrates how an int parameter can flag an
// operator as a postfix operation.
#include <iostream.h>
#include <conio.h>
class Piggy
{
private:
float Savings;
public:
Piggy() { Savings = 25; }
void ShowSavings() const;
void operator++();
void operator++(int);
};
void main()
{
clrscr();
Piggy Tom;
Tom.ShowSavings();
++Tom;
Tom.ShowSavings();
Tom++;
Tom.ShowSavings();
getch();
}
void Piggy::operator++(int)
{
cout << endl;
cout << "POSTFIX OPERATOR" << endl;
Savings += 10;
}
PROG2808.CPP OUTPUT
25 dollars saved
PREFIX OPERATOR
35 dollars saved
POSTFIX OPERATOR
45 dollars saved
You may have thought that the only difference in the two operator++ functions is
the prefix and postfix calling emphasis. Aside from that distinction both
functions increment in the same manner. This is not a requirement. The next
program example shows that you can have two operator functions with the same
name, operator++, yet each function increments with different amounts.
// PROG2809.CPP
// This program shows that it is not necessary for the prefix
// and postfix operator functions to perform in the same manner.
#include <iostream.h>
#include <conio.h>
class Piggy
{
private:
float Savings;
void main()
{
clrscr();
Piggy Tom;
Tom.ShowSavings();
++Tom;
Tom.ShowSavings();
Tom++;
Tom.ShowSavings();
getch();
}
void Piggy::operator++()
{
cout << endl;
cout << "PREFIX OPERATOR" << endl;
Savings += 10;
}
void Piggy::operator++(int)
{
cout << endl;
cout << "POSTFIX OPERATOR" << endl;
Savings += 20;
}
PROG2809.CPP OUTPUT
25 dollars saved
PREFIX OPERATOR
35 dollars saved
POSTFIX OPERATOR
55 dollars saved
Since we are brave enough to alter ShowSavings we can also add some color to
the Piggy constructor and use an int parameter to enter the amount of the initial
savings account. This is realistic. Not everybody starts their piggybank with the
same amount of cash. Program PROG2810.CPP shows these two improvements
along with a companion for Tom.
// PROG2810.CPP
// This program adds a parameter to the ShowSavings function to
// help identify the object. The constructor is also altered to
// specify an initial savings amount.
#include <iostream.h>
#include <conio.h>
#include "APSTRING.H"
class Piggy
{
private:
float Savings;
public:
Piggy(int);
void ShowSavings(apstring Name) const;
void operator++();
};
void main()
{
clrscr();
Piggy Tom(100);
Piggy Sue(200);
Tom.ShowSavings("Tom");
Sue.ShowSavings("Sue");
++Tom;
++Sue;
Tom.ShowSavings("Tom");
Piggy::Piggy(int Temp)
{
Savings = Temp;
}
void Piggy::operator++()
{
Savings += 10;
}
PROG2810.CPP OUTPUT
// PROG2811.CPP
// This program demonstrates that the performance of an operator
// function is not necessarily logically tied to its operator.
#include <iostream.h>
#include <conio.h>
class Piggy
{
private:
float Savings;
public:
Piggy()
{ Savings = 25; }
void operator++()
{ Savings--; }
void operator--()
{ Savings++; }
void ShowSavings() const
{ cout << Savings << " dollars saved" << endl; }
};
void main()
{
clrscr();
Piggy Tom;
Tom.ShowSavings();
++Tom;
Tom.ShowSavings();
--Tom;
Tom.ShowSavings();
getch();
}
PROG2811.CPP OUTPUT
25 dollars saved
24 dollars saved
25 dollars saved
Arithmetic assignment operators are very popular operators in C++. They are the
shortcut notations where K += 12 is used to imply K = K + 12 or K *= 5 is a
shortcut for K = K * 5. This type of convenience would be very nice to add to
user defined classes and the operator function implements this very easily. The
logic is identical to the unary operator++ or operator-- functions. The primary
difference is that a parameter is necessary to pass the value that must be
incremented by the function. A typical operator+= looks as follows:
// PROG2812.CPP
// This program shows how to implement operator functions for
// += and -= as well as passing parameters to operator functions.
#include <iostream.h>
#include <conio.h>
#include "APSTRING.H"
class Piggy
{
private:
float Savings;
public:
Piggy(int);
void ShowSavings(apstring Name) const;
void operator+= (float Money) { Savings += Money; }
void operator-= (float Money) { Savings -= Money; }
};
Piggy::Piggy(int Temp)
{
Savings = Temp;
}
PROG2812.CPP OUTPUT
// PROG2813.CPP
// This program shows how to call the overloaded += and -=
// operators using the dot-function notation.
#include <iostream.h>
#include <conio.h>
#include "APSTRING.H"
class Piggy
{
private:
float Savings;
public:
Piggy(int);
void ShowSavings(apstring Name) const;
void operator+= (float Money) { Savings += Money; }
void operator-= (float Money) { Savings -= Money; }
};
void main()
{
clrscr();
Piggy Tom(100);
Piggy Sue(200);
Tom.ShowSavings("Tom");
Sue.ShowSavings("Sue");
Tom.operator+=(300);
Sue.operator-=(100);
Tom.ShowSavings("Tom");
Sue.ShowSavings("Sue");
getch();
}
Piggy::Piggy(int Temp)
{
Savings = Temp;
}
So are there operator limitations? There are not many operators that cannot be
overloaded. Several operators have a special significance in C++ and any attempt
to give secondary, overloaded meaning to these operators will get you some
serious compiling errors. A small chart will follow with the few C++ operators
that cannot be overloaded, followed by a larger chart of all the many C++
operators that allow overloading. Do not be surprised if you see foreign looking
operators. Some of the strange operators will be make sense in some future
chapter.
+ - * / % ^ & | ~ ! =
+= -= *= /= %= ^= &= |=
< > <= >= << >> <<= >>= == !=
++ -- && || -> ->* , [] ()
By now you will see that the pattern for creating an operator function is quite
consistent. In the case of operator^(int) the intention is to take the Savings
attribute and raise it to the power of the int parameter. This type function is
// PROG2814.CPP
// This program demonstrates how to implement a function that does
// not exist in C++. In this example ^ is used to create
// a power operator.
#include <iostream.h>
#include <conio.h>
#include <math.h>
#include "APSTRING.H"
class Piggy
{
private:
float Savings;
public:
Piggy(int);
void ShowSavings(apstring Name) const;
void operator^ (float Power)
{ Savings = pow(Savings,Power); }
};
void main()
{
clrscr();
Piggy Tom(2);
Piggy Sue(5);
Tom.ShowSavings("Tom");
Sue.ShowSavings("Sue");
Tom^5;
Sue^3;
Tom.ShowSavings("Tom");
Sue.ShowSavings("Sue");
getch();
}
Piggy::Piggy(int Temp)
{
Savings = Temp;
}
PROG2814.CPP OUTPUT
Many pages can be filled up with examples that show all types of exotic
functions. Examples are good but at this stage you should be able to use your
imagination and try out some functions on your own. This chapter will by no
means give you the complete array of operator possibilities. That could fill a
book, but it my intention to show you a large variety of operator functions so that
you can use them comfortably in any of your own classes.
Every overloaded operator function you have seen has been a void function, and
these functions have done just fine for the examples presented. There are
situations where a void function cannot handle the required operation. You will
need some type of return operator function.
Put on your seatbelt because we are about to press down on the gas and make this
overloaded journey just a bit more interesting. Do not be afraid to reread some of
these sections, because it is unlikely that everything will soak in the first time.
Void functions have worked nicely for situations like K++, --K, K+=10, and
other similar operations. What about a statement like K = L++ ? Can that work
The statement K = L++ works fine with K and L both integers. The statement
will not compile if K and L are objects of the Piggy class. The compiler will
respond with a message like not an allowed type. Such an error message should
not be a total surprise. You start with a void function like operator++ and use it
to alter Savings. But the function is void, and cannot be used as a value in an
assignment program statement. Program PROG2815.CPP should convince you
that it is not possible to use overloaded void functions in an assignment.
// PROG2815.CPP
// This program attempts to use the operator++ function in an
// assignment statement. The program does not compile because
// a return function is required for the assignment.
#include <iostream.h>
#include <conio.h>
#include "APSTRING.H"
class Piggy
{
private:
float Savings;
public:
Piggy();
Piggy(int);
void ShowSavings(apstring Name) const;
void operator++();
};
void main()
{
clrscr();
Piggy Tom(200);
Piggy Sue(1400);
Tom.ShowSavings("Tom");
Sue.ShowSavings("Sue");
Tom = ++Sue;
Tom.ShowSavings("Tom");
getch();
}
Piggy::Piggy(int Temp)
{
Savings = Temp;
}
void Piggy::operator++()
{
cout << endl;
cout << "OPERATOR FUNCTION" << endl;
Savings += 10;
Piggy Temp;
Temp.Savings = Savings;
}
PROG2815.CPP OUTPUT
Compiling PROG2815.CPP:
Error PROG2815.CPP 31: Not an allowed type
The compiler stops at the statement Tom = ++Sue; and complain that this is Not
an allowed type. So we need a new approach to handle this problem. If you are
going to assign one object to another object than a return function is needed. In
our earlier example the operator++ function needs to return a Piggy type.
Returning a type sounds easy, but watch out that you do not mistakenly return the
data type rather than a variable of a type. You may declare a function with the
heading int Hello(float) but later in the function body you will not use return int.
Your function will use a statement like return 5 or return X, or any other
statement that returns an integer value.
So how do we return a value of type Piggy? You cannot say return Piggy. That
was just established as a problem. Take a look at program example
// PROG2816.CPP
// This program demonstrates how to use an operator function that
// returns an object. In this example a temporary object is used
// to return an object.
#include <iostream.h>
#include <conio.h>
#include "APSTRING.H"
class Piggy
{
private:
float Savings;
public:
Piggy();
Piggy(int);
void ShowSavings(apstring Name) const;
Piggy operator++();
};
void main()
{
clrscr();
Piggy Tom(200);
Piggy Sue(1400);
Tom.ShowSavings("Tom");
Sue.ShowSavings("Sue");
Tom = ++Sue;
Tom.ShowSavings("Tom");
Sue.ShowSavings("Sue");
getch();
}
Piggy::Piggy()
{
Savings = 0;
}
Piggy::Piggy(int Temp)
{
Savings = Temp;
}
Piggy Piggy::operator++()
{
cout << endl;
PROG2816.CPP OUTPUT
OPERATOR FUNCTION
The secret of the operator++ function is the locally declared Temp of type
Piggy. The function performs the same type of incrementing with the Savings
attribute as the previous operator++ function. After Savings is incremented a
new Piggy object is constructed, appropriately called Temp. The only
information of concern is Savings and that value is assigned to the Savings field
of the Temp object. The function finishes with return Temp and we are happy .
You return a value of some data type, not the data type itself.
It is also possible to create a return function without defining a local object. You
can use the trusty this pointer to help out. Have you forgotten about the this
pointer? You know that strange pointer explained in the encapsulation chapter.
Perhaps a very brief refresher will clear this (pun intended) up. The next program
displays the memory locations of an object defined in the main function as well as
the memory location of the this pointer in several member functions. The
program example demonstrates that the memory locations of the new object and
the invisible this pointer are identical. In other words, the this pointer keep track
of the current object in an invisible, background sort-of-way.
// PROG2817.CPP
// This program reviews the "this" pointer.
#include <iostream.h>
#include <conio.h>
#include "APSTRING.H"
class Piggy
{
public:
Piggy();
void Action1();
void Action2();
};
void main()
{
clrscr();
Piggy Tom;
cout << endl;
cout << "MAIN FUNCTION" << endl;
cout << "Address of Tom " << &Tom << endl;
getch();
Tom.Action1();
Tom.Action2();
getch();
}
Piggy::Piggy()
{
cout << endl;
cout << "CONSTRUCTOR CALL" << endl;
cout << "Address of this " << this << endl;
}
void Piggy::Action1()
{
cout << endl;
cout << "ACTION1 CALL" << endl;
cout << "Address of this " << this << endl;
}
PROG2817.CPP OUTPUT
CONSTRUCTOR CALL
Address of this 0x8f5bfff4
MAIN FUNCTION
Address of Tom 0x8f5bfff4
ACTION1 CALL
Address of this 0x8f5bfff4
ACTION2 CALL
Address of this 0x8f5bfff4
Great, the this business has been recalled or refreshed, and if you are still
confused you need to go back several chapters and review that material. There
was more detail about this this pointer in the encapsulation chapter. Now
assuming that there is a good understanding of the this pointer, the issue of
returning an object becomes possible without any need for a temporary object
definition. Be aware that the program example below is not complete.
// PROG2818.CPP
// This program demonstrates how to use an operator function that
// returns an object. In this example the "this" pointer is used
// to return an object.
#include <iostream.h>
#include <conio.h>
#include "APSTRING.H"
class Piggy
{
Piggy Piggy::operator++()
{
cout << endl;
cout << "OPERATOR FUNCTION" << endl;
Savings += 10;
return *this;
}
Program PROG2818.CPP was not shown in its entirety, nor is output shown.
The output and program is identical to the previous program. The only difference
is the operator++ function, which uses this. The most common problem that
happens is using return this rather than return *this.
Piggy Piggy::operator++()
{
cout << endl;
cout << "OPERATOR FUNCTION" << endl;
Savings += 10;
Piggy Temp;
Temp.Savings = Savings;
return Temp;
}
Piggy Piggy::operator++()
{
cout << endl;
cout << "OPERATOR FUNCTION" << endl;
Savings += 10;
return *this;
}
How about a total switch to something different like binary operators. In the
beginning of this chapter I showed how neatly you could do stuff, such as matrix
multiplication with a statement like M3 = M1 * M2; assuming that you have
three objects here. This multiplication statement is very common in mathematics,
but it is not anything you have seen with overloaded operators so far.
Overloaded binary operators behave very differently and require a syntax that
introduces a variety of new twists to the overloaded operator story. What type of
stuff would you expect? For starters you will need to work with return functions.
Does that make sense? Think of a binary operator statement, such as the one
above. The result of the binary operation is assigned to an object. The last
section showed that such an assignment is only possible if you use a return
function. This part is not so tough and you know two ways to achieve a return
function: using a temporary object, and using the this pointer.
Consider the following operator function for the binary plus operator. Does the
function not seem quite logical. Parameter LHS means Left Hand Side and
parameter RHS means Right Hand Side. These parameters are compared to the
plus operator. In other words, in the program statement X = Y + Z, variable X
gets the return value of the function, the operand Y is LHS and Z is RHS.
The example above would work just lovely if C++ used the same meaning for
LHS and RHS as the function above. Too bad, C++ has different ideas, and the
first big surprise is that the binary operator+ function uses only one parameter.
Somehow LHS is swinging in the breeze somewhere and totally forgotten.
Now remember that a member function always contains a this pointer and the
this pointer identifies some object that is currently in scope. C++ has decided that
the Left Hand Side (LHS) of a binary operation will be represented by the this
pointer. Furthermore, you know that this does not require parameter passing.
The clever this pointer just shows up, invited or not. Now considering the special
job of the this pointer the correct function below is not all that different from the
assumed function above. Any concerns about the const ampersand reference
parameter stuff? Hopefully not. At several occasions a point was made that data
structures should be passed by reference for efficiency sake. At the same time
you were told that it is wise to use const parameters if data values do not change.
With some new information under your belt you are now ready to digest program
PROG2819.CPP that shows how to use the operator+ binary function. In
particular, notice how the main function shows a program statement with a binary
operation that is identical in appearance to any mathematical operation.
#include <iostream.h>
#include <conio.h>
#include "APSTRING.H"
class Piggy
{
private:
float Savings;
public:
Piggy();
Piggy(int);
void ShowSavings(apstring Name) const;
Piggy operator+(const Piggy & RHS);
};
void main()
{
clrscr();
Piggy Tom(200);
Piggy Sue(1400);
Piggy Larry(0);
Tom.ShowSavings("Tom");
Sue.ShowSavings("Sue");
Larry.ShowSavings("Larry");
Larry = Tom + Sue;
Tom.ShowSavings("Tom");
Sue.ShowSavings("Sue");
Larry.ShowSavings("Larry");
getch();
}
Piggy::Piggy()
{
Savings = 0;
}
Piggy::Piggy(int Temp)
{
Savings = Temp;
}
PROG2819.CPP OUTPUT
The program output displays the desired result. The plus operator is meant to
achieve the addition of Tom and Sue such that Larry gets the sum of their
savings. Larry is happy, but are you perhaps surprised that my program example
returned to using the local, temporary object in the operator+ function? Did we
not establish that this can handle the situation nicely with less program
// PROG2820.CPP
// This program demonstrates how to implement a binary + operator
// using dot-function notation.
#include <iostream.h>
#include <conio.h>
#include "APSTRING.H"
class Piggy
{
private:
float Savings;
public:
Piggy();
Piggy(int);
void ShowSavings(apstring Name) const;
Piggy operator+(const Piggy &RHS);
};
void main()
{
clrscr();
Piggy Tom(200);
Piggy Sue(1400);
Piggy Larry(0);
Tom.ShowSavings("Tom");
Sue.ShowSavings("Sue");
Larry.ShowSavings("Larry");
Larry = Tom.operator+(Sue); // Larry = Tom + Sue
Tom.ShowSavings("Tom");
Sue.ShowSavings("Sue");
Larry.ShowSavings("Larry");
getch();
}
Piggy::Piggy()
{
Savings = 0;
}
Piggy::Piggy(int Temp)
{
PROG2820.CPP OUTPUT
Anytime that dot.function notation is used, you need to use some object variable
followed by a dot and then the operator function being used. This may help to
explain why there is only one parameter passed with a binary operator. Since the
this pointer take care of the LHS, it is quite easy to sneak in a side effect.
Program PROG2821.CPP is very similar to the previous program, except for the
different implementation of the overloaded operator+ function. Your good
friend the this pointer is in charge. Only the different operator+ function is
shown along with the sample output execution. Take a close look at the execution
and see if anything appears odd at all.
// PROG2821.CPP
// This program uses *this to return information with the
PROG2821.CPP OUTPUT
Apparently Larry is not the only one who is happy. Somehow Tom has benefited
rather nicely from this addition process. Returning *this may seem like a good
idea, but it requires that you alter (*this).Savings to return the proper results to
Larry. But there is a bad side effect. The this pointer keeps track of the LHS of
the binary operation, which in this case happens to be Tom. You see the
interesting situation that we achieve the correct mathematical result, but the same
result shows up in an unwanted place. This proves that sometimes constructing a
temporary object is required.
Binary Operator Notes
Correct Example:
Incorrect Example:
It is not always necessary for a return operator function to return some object
value. This was shown earlier for a good reason because you must return an
object value for any overloaded binary operators. There are other operations that
require a return function, but a Boolean value needs to be returned, and these
functions are the comparison operators.
Included in this group are equal, not equal, greater than, less than, greater than or
equal, and less than or equal. The assumption is that you will use the proper
By now you have seen the pattern of showing one example with the
understanding that the same logic applies to other operators of the same category.
Program PROG2822.CPP shows how to declare an equality comparison with the
operator== function. Equality in this case means that two Piggy objects each
have the same amount saved.
// PROG2822.CPP
// This program demonstrates how to implement a comparison
// function. This example uses the operator== function to
// check if the amount of savings is equal.
#include <iostream.h>
#include <conio.h>
#include "APSTRING.H"
class Piggy
{
private:
float Savings;
public:
Piggy(int);
void operator+=(int);
bool operator==(const Piggy & RHS) const;
};
void main()
{
clrscr();
Piggy Tom(200);
Piggy Sue(200);
if (Tom == Sue)
cout << "Tom's savings and Sue's savings are equal" << endl;
else
cout << "Tom's savings and Sue's savings are not equal"
<< endl;
Tom += 1000;
Sue += 500;
if (Tom == Sue)
Piggy::Piggy(int Temp)
{
Savings = Temp;
}
PROG2822.CPP OUTPUT
Note that the LHS and RHS type of logic is also showing up with the
operator== function. Once again, the LHS is carried by the this pointer and the
RHS of the equality comparison is passed as a parameter.
#include <iostream.h>
#include <conio.h>
#include "APSTRING.H"
class Piggy
void main()
{
clrscr();
Piggy Tom(200);
if (Tom == 200)
cout << "Tom has saved $200.00" << endl;
else
cout << "Tom does not have $200.00 saved" << endl;
Tom += 100;
if (Tom == 200)
cout << "Tom has saved $200.00" << endl;
else
cout << "Tom does not have $200.00 saved" << endl;
getch();
}
Piggy::Piggy(int Temp)
{
Savings = Temp;
}
PROG2823.CPP OUTPUT
Example:
This section will look suspiciously like the a section straight from the copy
constructor section of the last chapter. C++ has a default assignment operator
that works with classes. This means that it is possible to define two objects of the
same class and at some later point assign one object to another object with the
assignment ( = ) operator.
#include <iostream.h>
#include <conio.h>
class Pointers
{
public:
int *IntPtr;
public:
Pointers();
~Pointers();
void ShowData(int N) const;
};
void main()
{
clrscr();
Pointers P1;
cout << "IN MAIN FUNCTION" << endl;
P1.ShowData(1);
CreateP2(P1);
cout << "IN MAIN FUNCTION" << endl;
P1.ShowData(1);
}
Pointers::Pointers()
{
IntPtr = new int;
*IntPtr = 12345;
}
Pointers::~Pointers()
{
cout << endl;
delete(IntPtr);
}
PROG2824.CPP OUTPUT
Object P1
IntPtr 0x8f8bde0
*IntPtr 12345
Object P2
IntPtr 0x8f8bde0
*IntPtr 12345
Object P2
IntPtr 0x8f8bde0
*IntPtr 1000
OBJECT IS DESTROYED
Object P1
IntPtr 0x8f8bde0
*IntPtr 3548
OBJECT IS DESTROYED
The sample output shows the problem. Object P2 becomes a copy of P1 and then
some change occurs in P2 and P2 leaves scope before P1 is finished. Suddenly
P1 finds itself swinging in the breeze and staring at some weird value.
The problem is solved in the same manner as it was done with the default copy
constructor. Do not rely on the default stuff, create your own. In this case you
must create your own overloaded operator= function that will make a proper
deep copy of the source object. In this manner any change in the source object
will not alter values in the target object. The user-defined operator= is shown in
the next program example.
#include <iostream.h>
#include <conio.h>
class Pointers
{
public:
int *IntPtr;
public:
Pointers(); // constructor
~Pointers(); // destructor
void ShowData(int) const;
Pointers operator=(const Pointers &Obj);
};
void main()
{
clrscr();
Pointers P1;
cout << "IN MAIN FUNCTION" << endl;
P1.ShowData(1);
CreateP2(P1);
cout << "IN MAIN FUNCTION" << endl;
P1.ShowData(1);
}
Pointers::Pointers()
{
IntPtr = new int;
*IntPtr = 12345;
}
Pointers::~Pointers()
{
cout << endl;
delete(IntPtr);
}
PROG2825.CPP OUTPUT
Object P1
IntPtr 0x8f8bde0
*IntPtr 12345
Object P2
IntPtr 0x8f8bde0
*IntPtr 12345
Object P2
IntPtr 0x8f8bde0
*IntPtr 1000
OBJECT IS DESTROYED
Object P1
IntPtr 0x8f8bde0
*IntPtr 12345
OBJECT IS DESTROYED
Free Function
class Example
{
private:
int X;
public:
void SetX(int N);
int GetX() const;
};
Take a look at RHS and look at the caution employed. The object is passed by
reference to avoid inefficient copying of data and inefficient memory usage. At
the same time const does an excellent job insuring that RHS only pays a visit,
snoops around, but does not get modified. You have seen similar precautions
used with accessor member functions that use a const on the right side of the
function heading.
So what has been done for the left hand side? Basically, nothing and there is no
protection in sight. Now if we could create a function, such as the one below than
the exact same protection could be afforded to LHS.
Still the idea has merit and let us explore the possibility of using an overloaded
operator function that is not a member function. This so called free function is
also free of the requirement of a single parameter. Maybe that could be a solution
to the problem that we are trying to solve.
// PROG2826.CPP
// This program attempts to create an overloaded free function.
// The program does not compile because Savings is not accessible.
#include <iostream.h>
#include <conio.h>
#include "APSTRING.H"
class Piggy
{
public:
Piggy(int N)
: Savings(N) { }
void ShowSavings(apstring Name) const;
private:
float Savings;
};
void main()
{
clrscr();
Piggy Tom(200);
Piggy Sue(1400);
Piggy Larry(0);
Tom.ShowSavings("Tom");
Sue.ShowSavings("Sue");
Larry.ShowSavings("Larry");
Larry = Tom + Sue;
Tom.ShowSavings("Tom");
Sue.ShowSavings("Sue");
Larry.ShowSavings("Larry");
getch();
}
PROG2826.CPP OUTPUT
Compiling PROG2826.CPP:
Error PROG2826.CPP 51: 'Piggy::Savings' is not
accessible
That was a lovely idea, but our clever program does not even compile. Still, that
is not such a great shock because we are rather casually trying to access Savings,
and that happens to be private data. It is fine to depart from the class boundaries,
but do not expect to access any private class members.
// PROG2827.CPP
// This program solves the Savings access problem by making the
// binary operator+ function a friend function. This approach
// is not recommended by the AP committee which strongly
// discourages the use of a friend function because using friend
// functions violates encapsulation.
#include <iostream.h>
#include <conio.h>
#include "APSTRING.H"
class Piggy
{
public:
Piggy(int N)
: Savings(N) { }
void main()
{
clrscr();
Piggy Tom(200);
Piggy Sue(1400);
Piggy Larry(0);
Tom.ShowSavings("Tom");
Sue.ShowSavings("Sue");
Larry.ShowSavings("Larry");
Larry = Tom + Sue;
Tom.ShowSavings("Tom");
Sue.ShowSavings("Sue");
Larry.ShowSavings("Larry");
getch();
}
PROG2827.CPP OUTPUT
There is program output, and the output is correct. So what is happening with the
magic friend word. If you look inside the Piggy class you see that operator+ is
present there. At least a prototypes is present there preceded by the odd-looking
word, friend. If you are looking at this on the computer, you will notice that
friend is bold and appears to be a reserved word in C++.
You are quite correct. You are looking at a free function that has special
privileges. The operator+ is a free function, but it is also a friend function of the
Piggy class. This was accomplished by placing the prototype of operator+ inside
the Piggy class preceded by the friend keyword. C++ is happy now and allows a
special exception, since you are a friend I will let you have access to the private
data of this class.
Friend Function
This friend business is all quite wonderful but you will be pleased to know that
the College Board APCS Committee is not excited about the use of friend
functions. The friend concept will not be tested and the use of friends is
Well let us not give up and see if there is another way to approach this problem.
Perhaps we can accomplish the same type of idea without the use of the forbidden
friend functions. After all, it is very legitimate to have public member functions
that access private data, and such functions can indirectly access the necessary
data for your free function. Such a solution is provided in PROG2828.CPP, and
this approach avoids the use of friend functions.
In preparation for the APCS Examination you will be studying a case study that
has been prepared by the College Board. This case study involves a rather
substantial amount of C++ source code and it has many overloaded operators.
You will find that free functions are used heavily and the access of private data
with the free functions is done in a manner that is similar to the next program
example. In other words, pay attention because you will see this again.
// PROG2828.CPP
// This program demonstrates how to use a binary operator free
// function without using friends. This approach uses a new
// GetSavings function and operator+= function to access data.
#include <iostream.h>
#include <conio.h>
#include "APSTRING.H"
class Piggy
{
public:
Piggy(int N)
: Savings(N) { }
void operator+= (int N)
{ Savings += N; }
void ShowSavings(apstring Name) const;
int GetSavings() const
{ return Savings; }
private:
float Savings;
};
void main()
{
clrscr();
Piggy Tom(200);
Piggy Sue(1400);
Piggy Larry(0);
Tom.ShowSavings("Tom");
/***
***/
PROG2828.CPP OUTPUT
A second operator+ is shown as a slight variation on the same theme. The logic
is identical, but one program statement is avoided. The Temp object is
constructed with the LHS parameter. This invokes the copy constructor and
Temp will be a copy of LHS. A single call to operator+= then adds the RHS
and the function is ready to return the requested sum.
You need to realize that it is not just a matter of "beefing" up the left hand side
(LHS) with the same protection as the (RHS) that motivates the use of free
functions. There is another important issue involved that makes free functions
with two parameters very important. The concern is the nature of the operands,
specifically non-object operands.
Consider the situation of our Piggy class again, and look at the three different
possibilities that can occur with a binary operator. I am talking about the three
types of operands that can be used with a binary operator. Let us try to stick to
single-parameter operator functions and see what is happening. I am intentionally
showing the dot.function notation below each statement, to help clarify the
potential problem.
The LHS (Tom) is an object tied to the this pointer, and the RHS (Larry) is an
object that can be passed as the single paremeter. This situation does not cause a
problem for a single-parameter operator+ member function.
The LHS (Tom) is an object tied to the this pointer, and the RHS (500) can be
passed as a single parameter. This situation does not cause a problem for a single-
parameter operator+ member function.
The LHS (500) is not an object and cannot be tied to the this pointer. The RHS
(Tom) can be passed as a single parameter, but this is only possible if there is an
object for the LHS. This situation causes major problems for a single-parameter
operator+ member function, and is a major reason for creating two-parameter free
overloaded operators.
There are some special overloaded operator functions that can only be free
functions. You have used these operators since the beginning of time and I am
talking about the stream operators, often called chevrons. More formally the
input operator >> is called the extraction operator and the output operator << is
called the insertion operator.
It may well be desirable to overload these input/output operators with the use of
some class. You can then have the added benefit of customizing i/o operations to
Let us start by looking at a program that provides output. Once again it is the
trusty Piggy class that helps us out here. We are so excited about savings that we
want to be able to display the amount of savings by simply using a statement like:
In such a case Tom is a member of the Piggy class and the result should be a
display of the amount of money that Tom has saved. Program PROG2829.CPP
demonstrates how to implement such an overloaded operator function.
// PROG2929.CPP
// This program demonstrates an overloaded insertion operator.
// It is not possible to implement this operator as a member
// function.
#include <iostream.h>
#include <conio.h>
class Piggy
{
public:
Piggy(int N)
: Savings(N) { }
float GetSavings() const
{ return Savings; }
private:
float Savings;
};
void main()
{
clrscr();
Piggy Tom(200);
cout << "Tom: " << Tom << endl;
Piggy Sue(1400);
cout << "Sue: " << Sue << endl;
getch();
}
PROG2829.CPP OUTPUT
Tom: 200
Sue: 1400
The actual function body is quite short. The public member function GetSavings
is used to access the private data Savings. The value of Savings is then inserted
into the output stream for display.
Do you notice that there are two different classes passed as parameters. There is
one object (Obj) of the Piggy class and there is also one object (OS) of the
ostream class. These two classes cannot live together declared inside the same
class. We must then either use the friend function approach or use some helper
function to access the data. Either way we have to use a free function.
The final program example adds the extraction operator. We can now do both
input and output with objects of our trusty Piggy class. The last example of this
chapter, program PROG2830.CPP demonstrates how to overload the extraction
operator.
You may feel relieved that this chapter has reached its final example. However,
there is still some future business to be handled with overloaded operators. You
will need to learn some other concepts first and then we will return to this topic.
Overloaded operators are powerful and help to make C++ a popular language. It
is not an easy topic, and it will take some digestion time to feel comfortable.
// PROG2830.CPP
// This program adds a free overloaded extraction operator
// function to the previous program.
#include <iostream.h>
#include <conio.h>
class Piggy
{
public:
void main()
{
clrscr();
Piggy Tom(200);
cout << "Tom: " << Tom << endl;
Piggy Sue(1400);
cout << "Sue: " << Sue << endl;
cout << "Enter two values ";
cin >> Tom >> Sue;
cout << "Tom: " << Tom << endl;
cout << "Sue: " << Sue << endl;
getch();
}
PROG2830.CPP OUTPUT
Tom: 200
Sue: 1400