Professional Documents
Culture Documents
Chapter XXXI
Chapter XXXI
Chapter XXXI
We are now hitting the home stretch of our OOP long distance run. There are
plenty of applications and discoveries left, but with this chapter you have reached
the end of introducing new OOP topics. The title of this chapter does not have a
nice single word like inheritance, templates or polymorphism. Some of this
chapter's topics are an continuing investigation of polymorphism topics, but it
does not seem right to title the chapter as such.
This introduction will not attempt to explain some general nature of the topics to
come. Unlike a topic such as inheritance or templates, this chapter has three
separate topics. Each one of the functions is part of object oriented programming,
but otherwise this chapter serves somewhat like a “clean-up” chapter in OOP that
takes care of several miscellaneous concepts.
Friend functions may seem like a strange title for a chapter section. Actually the
title is very appropriate because C++ has a reserved word, friend, which can be
used with member functions for special purposes. A function declared as friend
has special access powers. But details about these friend functions must wait a
bit. First, you need to look at a few program examples that will help to motivate
the need for a special type of function.
The first program example shows a program with a Frogs class. This class does
little besides keep track of a bunch of frogs. Our main concern is to see how data
in objects of the Frogs class are accessed. In program PROG3101.CPP the
private attribute, Count, of the Frogs class is accessed by the public actions,
GetCount, PutCount and ShowCount. This is a quick recall of the standard
way that private attributes have been accessed in previous program examples.
// PROG3101.CPP FRIEND1
// This program demonstrates access to private data Count using
// a ShowCount member function.
#include <iostream.h>
#include <conio.h>
class Frogs
{
private:
int Count;
public:
int GetCount() const
{ return Count; }
void PutCount(int C)
{ Count = C; }
void ShowCount() const
{ cout << "There are " << Count << " frogs." << endl; }
};
void main()
{
clrscr();
Frogs F;
F.PutCount(10);
F.ShowCount();
getch();
}
PROG3101.CPP OUTPUT
// PROG3102.CPP FRIEND2
// This program uses a non-member ShowCount function.
// It will not compile because function ShowCount does not
// have access to private attribute Count.
#include <iostream.h>
#include <conio.h>
class Frogs
{
private:
int Count;
public:
int GetCount() const
{ return Count; }
void PutCount(int C)
{ Count = C; }
};
void main()
{
clrscr();
Frogs F;
F.PutCount(10);
ShowCount(F);
getch();
}
You are probably wondering by now what the point is anyway. Let us see. First,
we have a fully functional program in a style that has worked just nicely for many
chapters. Then suddenly a program is presented with a strange non-member
// PROG3103.CPP FRIEND3
// This program demonstrates how it is possible to access
// the private Count attribute with a friend function.
#include <iostream.h>
#include <conio.h>
class Frogs
{
private:
int Count;
public:
int GetCount() const
{ return Count; }
void PutCount(int C)
{ Count = C; }
friend void ShowCount(Frogs);
};
void ShowCount(Frogs F)
{
cout << "There are " << F.Count << " frogs." << endl;
}
void main()
{
clrscr();
Frogs F;
F.PutCount(10);
ShowCount(F);
getch();
}
PROG3103.CPP OUTPUT
Example:
Some of you are less and less convinced about the logic here. It looks like you
have been presented with a convoluted way to access private data when the whole
point is not to be able to mess with the private segment of an object. We are
violating the sacred encapsulation of the class and basically forget the whole
information hiding business. Is there a reason for committing such a serious sin?
Strange as it may seem, there are some special situations that call for the power of
the friend function. Look at the manner that the binary plus operator is
overloaded in the next program example.
Now this program example shows a single parameter for a binary operation.
Recall how the nifty this pointer provides the left-hand-side operand and the
parameter takes care of the right-hand-side operand. This also explain the naming
of the parameter as RHS.
// PROG3104.CPP FRIEND4
// This program demonstrates how to implements a traditional
// overloaded operator function for the binary + operation.
#include <iostream.h>
#include <conio.h>
#include "APSTRING.H"
class Piggy
{
private:
float Savings;
public:
Piggy(int S) { Savings = S; }
Piggy operator+(const Piggy &);
void ShowSavings(apstring) const;
};
void main()
{
clrscr();
Piggy Tom(800);
Piggy Sue(700);
Piggy Larry(0);
Tom.ShowSavings("Tom");
Sue.ShowSavings("Sue");
Larry.ShowSavings("Larry");
Larry = Tom + Sue;
Larry.ShowSavings("Larry");
getch();
}
PROG3104.CPP OUTPUT
// PROG3105.CPP FRIEND5
// This program demonstrates that you may not use two parameters
// with an overloaded binary operator+
#include <iostream.h>
#include <conio.h>
#include "APSTRING.H"
class Piggy
{
private:
float Savings;
public:
Piggy(int S) { Savings = S; }
Piggy operator+(const Piggy &, int);
void ShowSavings(apstring) const;
};
Hopefully, you see now what is happening. The nifty overloaded binary plus
operator has limitations. The binary operator has two operands and C++ wants
only one operand (the right hand side) to be provided as a parameter. You do that
and C++ will take care of the left hand side. Beyond that one parameter, no
additional parameters are allowed.
What comes to the rescue? Well it is your friend function. The problem of the
previous program is handled very nicely by declaring the operator+ function as
non-member friend function.
// PROG3106.CPP FRIEND6
// This program demonstrates how to implement a binary + operator
// with a non-member overloaded + operator function.
// This program works because the operator+ is friend function.
#include <iostream.h>
#include <conio.h>
#include "APSTRING.H"
class Piggy
{
private:
float Savings;
public:
void main()
{
clrscr();
Piggy Tom(800);
Piggy Larry(0);
Tom.ShowSavings("Tom");
Larry.ShowSavings("Larry");
Larry = Tom + 700;
Larry.ShowSavings("Larry");
getch();
}
PROG3106.CPP OUTPUT
Perhaps you are impressed with this friend function business. It is also possible
that you were quite happy before, and you see little use for this added oddity to
your growing OOP tool kit. Whatever your feelings about friend functions, do
remember the following:
// PROG3107.CPP Static1
// This program creates 3 Aardvark objects
// Access of independent objects has independent results
#include <iostream.h>
#include <conio.h>
class Aardvarks
{
private:
int Number;
int AardvarkCount;
public:
Aardvarks(int N, int C)
{ Number = N; AardvarkCount = C; }
void IncCount()
{ AardvarkCount++; }
int GetNumber() const
{ return Number; }
int GetCount() const
{ return AardvarkCount; }
};
void main()
{
clrscr();
Aardvarks A1(1,5);
Aardvarks A2(2,15);
PROG3107.CPP OUTPUT
C++, ever full of surprises, has the ability to define different objects of the same
class and keep the same data member common to all defined objects. In other
words, the data member now is no longer independent. The access and alteration
by any object will make a change to the same data member of the other objects.
We now have a situation where you can say that a static attribute does not
represent object information but class information. This will seem quite weird
because a class is a data type declaration and not a variable definition. Yet, that is
the manner in which static data members behave. In a normal situation every
definition of a new object creates new copies of the object’s attributes. The
values stored in those attributes can only be accessed in the scope of an object. In
other words, only from the moment that the object is constructed until the object
is destroyed, can the object’s attributes be accessed. This limitation changes with
a static member and access is now even possible after an object is destroyed.
Static data members are declared with the reserved word static. Additionally, the
static data member must be initialized in a global section of the program after the
class declaration. Program PRGO3108.CPP is written with the same logic as the
previous program. The main difference is that attribute AardvarkCount is now a
static data member. Compare the output execution of this program and the
previous program. You will see that the previous program incremented the three
different values used to define three different objects. In the case of this program
the output execution has some surprises in store for you.
// PROG3108.CPP Static2
// This program declares AardvarkCount as 'static'.
// The result is that AardvarkCount does not change, even
// as different Aardvark objects are accessed.
#include <iostream.h>
#include <conio.h>
void main()
{
clrscr();
Aardvarks A1(1,5);
Aardvarks A2(2,15);
Aardvarks A3(3,25);
IncrementAardvarks(A1);
IncrementAardvarks(A2);
IncrementAardvarks(A3);
getch();
}
PROG3108.CPP OUTPUT
Does this output make any sense to you? Let’s check it out. The first object is
initialized with 5 aardvarks, followed by the second object with 15 objects. The
last object is initialized with 25 objects. The output shows 26, 27 and 28 objects.
What is going on, and doesn’t static mean that there is no change? There are
some serious concerns with this program.
It may be difficult to see any value in this C++ feature. Picture some type of
video game. The video game features Rarps, which are strange creatures who are
trying to save the universe. Rarps are continuously created and destroyed
according to the rules of the game. At any time that you have no Rarps left, the
game is over, the universe is doomed, and you have lost. It makes sense that
every instance of a Rarp is an object, which can be constructed and destroyed.
The Rarp class also needs to store the number of Rarps created, since total
destruction means that the game is over. This track-keeping is easily
accomplished with a static RarpCount.
The next program example demonstrates that it is possible to access static data
member information after all objects are destroyed. A special function is used to
construct five different Aardvark objects. Every one of these objects is
destroyed when the function has finished executing. Then in the main function,
after all objects are no longer in scope - the static member can still be accessed.
You do need to realize that this access does has some special requirements. First,
you will note that AardvarkCount had to be switched to public. Second, the
class name with the scope resolution operator has to be used to identify the
location of this peculiar identifier.
// PROG3109.CPP STATIC3
// This program demonstrates that static member values can be
// accessed without an object. Even after every object is
// destroyed access is possible with the class identifier and
// the scope resolution operator.
#include <iostream.h>
#include <conio.h>
class Aardvarks
{
public:
static int AardvarkCount; // changed to public
Aardvarks()
{ cout << "Constructor" << endl; }
~Aardvarks()
{ cout << "Destructor" << endl << endl; }
void PutCount(int C)
{ AardvarkCount = C; }
void IncCount() const
{ AardvarkCount++; }
void Initialize()
{
Aardvarks Vark;
Vark.PutCount(5);
cout << Vark.GetCount() << " aardvarks." << endl;
getch();
}
void CreateAardvarks()
{
Aardvarks A;
A.IncCount();
cout << A.GetCount() << " aardvarks." << endl;
}
void main()
{
clrscr();
Initialize();
for (int K=1; K <= 4; K++)
{
CreateAardvarks();
getch();
}
cout << Aardvarks::AardvarkCount << " aardvarks." << endl;
getch();
}
PROG3109.CPP OUTPUT
Constructor
5 aardvarks
Destructor
Constructor
6 aardvarks
Destructor
Constructor
7 aardvarks
Destructor
Constructor
9 aardvarks
Destructor
9 aardvarks
Declaration example:
int Aardvarks::AardvarkCount = 0;
There are probably many alert students who are objecting right now. One sacred
rule of Object Oriented Programming has been violated. The previous program
provided information about AardvarkCount at the cost of making this attribute
public. We have lost encapsulation and information hiding is not doing so well
either. This is really bad. There must be a better way. Luckily, the ever brilliant
C++ developers also provide us with static functions, which can access static
data members.
We know that proper access of members in the private segment is with either
public functions of the same class or - in rare cases - with non-member friend
functions. This way information happily stays hidden in its private or protected
segment away from unwanted access.
Declaring and using static actions is not difficult. Precede the function
declaration with the reserved word static, and you are ready to go. There is no
special global initialization as you saw with static attributes.
// PROG3110.CPP STATIC4
// This program demonstrates how static member functions can
// access static data members when the data member's object is
// in scope and also after the data member's object is no
longer // in scope.
#include <iostream.h>
#include <conio.h>
class Aardvarks
{
private:
static int AardvarkCount;
public:
void Initialize()
{
Aardvarks Vark;
Vark.PutCount(5);
cout << Vark.GetCount() << " aardvarks." << endl;
getch();
}
void CreateAardvarks()
{
Aardvarks A;
A.IncCount();
cout << A.GetCount() << " aardvarks." << endl;
}
void main()
{
clrscr();
Initialize();
for (int K=1; K <= 4; K++)
{
CreateAardvarks();
getch();
}
cout << Aardvarks::GetCount() << " aardvarks." << endl;
getch();
}
PROG3110.CPP OUTPUT
Constructor
5 aardvarks
Destructor
Constructor
Constructor
7 aardvarks
Destructor
Constructor
8 aardvarks
Destructor
Constructor
9 aardvarks
Destructor
9 aardvarks
N = A.GetCount();
N = Aardvarks::GetCount();
The next, and last, program example with static class attributes and actions,
identifies each object as it is constructed and destroyed. This program is designed
to demonstrate and re-emphasize that each new object shares the one-and-same
“class-wide” static data member. It also proves that every object is destroyed and
it is still possible to access the AardvarkCount attribute.
// PROG3111.CPP STATIC5
// This program adds an Aardvarks ID number to keep track of the
// specific Aardvarks objects that are constructed and destroyed.
#include <iostream.h>
#include <conio.h>
class Aardvarks
{
private:
static int AardvarkCount;
int AardvarkID;
public:
Aardvarks(int N)
{ AardvarkID = N;
cout << "Creating Aardvark # " << AardvarkID << endl; }
~Aardvarks()
{ cout << "Destroying Aardvark # " << AardvarkID
<< endl << endl; }
void PutCount(int C)
{ AardvarkCount = C; }
void IncCount()
{ AardvarkCount++; }
void Initialize()
{
Aardvarks Vark(1);
Vark.PutCount(5);
cout << Vark.GetCount() << " aardvarks." << endl;
getch();
}
void CreateAardvarks(int K)
{
Aardvarks A(K);
A.IncCount();
cout << A.GetCount() << " aardvarks." << endl;
}
void main()
{
clrscr();
Initialize();
for (int K=2; K <= 5; K++)
{
CreateAardvarks(K);
getch();
}
cout << Aardvarks::GetCount() << " aardvarks." << endl;
getch();
}
PROG3111.CPP OUTPUT
Creating Aardvark # 1
5 aardvarks
Destroying Aardvark # 1
Creating Aardvark # 2
6 aardvarks
Destroying Aardvark # 2
Creating Aardvark # 3
7 aardvarks
Destroying Aardvark # 3
Creating Aardvark # 4
8 aardvarks
Destroying Aardvark # 4
9 aardvarks
Polymorphism Types
Apparently there is no let-up in the pre-occupation that computer science has with
peculiar vocabulary. Virtual functions is now the latest introduction of some
obscure sounding entity. What is a virtual function? How about, what is virtual
reality? That is a hot item these days. You can be hooked up to some special
device with a helmet covering your eyes, your hands are attached to special grips
and your feet are locked in some boots. The next thing you know is that you are
zooming down the ski slopes of Snowbird, Utah, and this is done during August
inside a Dallas, Texas building. This example implies that virtual is not real, but
it appears real. So, am I saying that a virtual function appears real, but it is not a
real function? Yes, that is pretty much what I am saying.
By the way be warned that this is not a good section to start at 1:30am after five
hours of studying with a brain that is fried to mush from fun with integral
Calculus. As always, this new topic will be introduced in stages, but it is a topic
that is better handled with a fresh mind and well rested nerve cells. Do nerve
cells rest? I do not know, but I do know that there have been many programming
problems that seemed impossible to solve late at night that became trivial first
thing in the morning. Yes, I am a morning person.
The first program example sets the stage, and this stage is not virtual at all. In this
program we start with a base class, Level1 and two derived classes, Level2A and
Level2B. Each one of three class declarations includes a Display function. It is
very important to realize that these three Display functions are not overloaded.
Each one of them has the exact same parameter list, which happens to be no
parameters. It will not be possible to identify the desired function by using
parameter list matching.
Now you have seen this business before. We are talking inheritance here, and
other methods are used for identification. Take a look at the program example
first and recall how C++ accomplishes this type of identification.
// PROG3112.CPP VIRTUAL1
// This program demonstrates how to call identically named
// Display functions using the "this" pointer and the
#include <iostream.h>
#include <conio.h>
class Level1
{
private:
public:
void Display() { cout << "Level1 Display" << endl; }
};
void main()
{
Level2A L2A;
Level2B L2B;
clrscr();
L2A.Display();
L2B.Display();
L2A.Level1::Display();
L2B.Level1::Display();
getch();
}
PROG3112.CPP OUTPUT
Level2A Display
Level2B Display
Level1 Display
Level2 Display
That was not that tricky. Was it? The display functions of Level2A and Level2B
were used properly because a statement like L2A.Display() makes life simple for
the C++ compiler. L2A is an object of Level2A and hence the compiler knows
which Display function is required.
What we are after is some clever means to indicate what is needed at the time that
we execute or run the program. Any clues? Well the one feature that you have
seen so far that has any power at runtime is the pointer. You have seen that most
data types get their memory space allocated at compile-time. They are static data
types and their memory space does not change at run-time. The nifty pointer just
happily grabs memory during execution with new and just as happily returns
memory with delete. Yes, the pointer is called dynamic because it can change
memory during program execution. Great, let us try to see what happens with a
pointer. In the past we have pointed to various data types and we have used
pointers to implement an Array class. The pointer accomplished the actual
storing of the array elements. So how do pointers help in this situation? Well, see
if you can digest program PROG3113.CPP and make sense of the pointer
business.
// PROG3113.CPP VIRTUAL2
// This program tries to use pointer variables to identify the
// particular Display function that needs to be called.
#include <iostream.h>
#include <conio.h>
class Level1
{
private:
public:
void Display() { cout << "Level1 Display" << endl; }
};
clrscr();
Ptr = &L1;
Ptr->Display();
Ptr = &L2A;
Ptr->Display();
Ptr = &L2B;
Ptr->Display();
getch();
}
PROG3113.CPP OUTPUT
Level1 Display
Level1 Display
Level1 Display
This program seems very unsatisfactory. The program code makes little sense,
and to make matters worse the program execution displays the exact same output
three times. You may understand little, but you do realize that this program
accessed the exact same function three times in a row. Apparently, the fancy
pointer footwork did little more than confuse students who did not require extra
assistance in getting confused.
The program was well intended. If we ignore the syntax specifics, here is what
was attempted to be done. Start with a pointer to a Level1 data type or class.
Then repeat the same process three times of storing the memory location of an
object in the variable pointer. Follow this with dereferencing the pointer to get
access to the Display function and we should be getting results. Now to get more
specific with the syntax look at the main function shown below. This function
will make the explanation easier and it will also be good to review some of the
pointer syntax that is used.
void main()
{
Level1 L1; // line 1
clrscr();
Ptr = &L1; // line 5
Ptr->Display(); // line 6
getch();
}
The secret is to use a base pointer, as we did in the prior program. Now when the
base pointer points to any derived object that contains a virtual function, C++
takes over and decided which function needs to be called. C++ makes this
decision based on the object pointed to by our trusty base pointer. Now, since this
process occurs at execution time, the choice of which function to call then
becomes a run-time polymorphism. You will see in the next program example
that the output execution is now as desired.
// PROG3114.CPP VIRTUAL3
// This program demonstrates that identifying function Show
// in the Base class as "virtual" creates a runtime polymorphism.
#include <iostream.h>
#include <conio.h>
class Level1
{
private:
public:
virtual void Display() { cout << "Level1 Display" << endl; }
};
void main()
{
Level1 L1;
Level1 *Ptr;
Level2A L2A;
Level2B L2B;
clrscr();
Ptr = &L2A;
Ptr->Display();
Ptr = &L2B;
Ptr->Display();
getch();
}
PROG3114.CPP OUTPUT
Level1 Display
Level2A Display
Level2B Display
The previous program example showed two classes derived from the same base
class. In that case the virtual function approach worked nicely. The next program
example sets up three classes in three inheritance levels. Only the highest level
requires the virtual declaration. You will see that this approach also works.
// PROG3115.CPP VIRTUAL4
// This program shows that a virtual function is inherited
// in multi-level inheritance.
#include <iostream.h>
#include <conio.h>
class Level1
{
private:
public:
virtual void Display() { cout << "Level1 Display" << endl; }
};
void main()
{
Level1 L1;
Level1 *Ptr;
Level2 L2;
Level3 L3;
clrscr();
Ptr = &L1;
Ptr->Display();
Ptr = &L2;
Ptr->Display();
Ptr = &L3;
Ptr->Display();
getch();
}
PROG3115.CPP OUTPUT
Level1 Display
Level2 Display
Level3 Display
We need to look at a few more examples with special situations. What if the
pointer points at an object and attempts to call a function that does not exist in that
object? The function being called is a virtual function, and it does exist at a
higher level. In such a case, C++ goes to the higher level and executes that
function.
// PROG3116.CPP VIRTUAL5
// This program demonstrates that the base class function is used
// if the requested function is not a member of the derived class.
class Level1
{
private:
public:
virtual void Display() { cout << "Level1 Display" << endl; }
};
void main()
{
Level1 L1;
Level1 *Ptr;
Level2 L2;
Level3 L3;
clrscr();
Ptr = &L1;
Ptr->Display();
Ptr = &L2;
Ptr->Display();
Ptr = &L3;
Ptr->Display();
getch();
}
PROG3116.CPP OUTPUT
Level1 Display
Level2 Display
Level2 Display
It may be strange to create a virtual function, and then not use such a function in
derived classes. After all, the whole point is that the derived classes can override
the functionality of the higher-level function. In this case, I am trying to
anticipate the frequent what if questions that students tend to ask. Questions need
to be answered even if they pertain to situations that really do not occur
frequently.
// PROG3117.CPP VIRTUAL6
// This program goes up one more level to show that the highest
// base class is used if a function is not available.
#include <iostream.h>
#include <conio.h>
class Level1
{
private:
public:
virtual void Display() { cout << "Level1 Display" << endl; }
};
void main()
{
Level1 L1;
Level1 *Ptr;
Level2 L2;
Level3 L3;
clrscr();
Ptr = &L1;
Ptr->Display();
Ptr = &L2;
Ptr->Display();
Ptr = &L3;
Ptr->Display();
Level1 Display
Level1 Display
Level1 Display
The main point of using virtual functions is to provide a common interface with a
selection of functions that can be altered at lower-level derived stages. Such an
approach provides flexibility. You may wish to set up a base class that includes
functions of no particular consequence. Now these functions are meant to
perform important procedures at some later-derived-class-level, but not now. It
almost appears that the virtual function is a place holder for a name and does not
need to provide any executable code.
C++ provides for such a situation and you can declare a virtual function without
using any executable code at all. When you do that, the function is called a pure
virtual function, and a class with such a function is called an abstract class.
Example:
// PROG3118.CPP VIRTUAL7
// This program shows how to use a "pure" virtual function.
#include <iostream.h>
#include <conio.h>
class Level1
{
private:
public:
virtual void Display() = 0;
};
void main()
{
Level1 *Ptr;
Level2 L2;
Level3 L3;
clrscr();
Ptr = &L2;
Ptr->Display();
Ptr = &L3;
Ptr->Display();
getch();
}
PROG3118.CPP OUTPUT
This chapter will finish with a slightly more meaningful program that uses a
virtual function. All the previous programs focused strictly on the required syntax
and program execution. This last program starts with a base class, called
Abstract, which contains the virtual function, Display. There are three derived
classes, Rectangle, Triangle and Square. Each one of the derived classes needs
a member functions to display its own particular design. The virtual function is
set up as a pure virtual function and pointers keep track at run-time to identify the
proper function to be called.
// PROG3119.CPP VIRTUAL8
// This program shows how to set up some general abstract
// interface for a group of classes with virtual functions.
#include <iostream.h>
#include <conio.h>
class Abstract
{
private:
public:
virtual void Display() = 0;
};
void main()
{
clrscr();
Abstract *Ptr;
Rectangle R(50,10);
Square S(12);
Triangle T(15);
clrscr();
Ptr = &R;
Ptr->Display();
getch();
clrscr();
Ptr = &S;
Ptr->Display();
getch();
clrscr();
Ptr = &T;
Ptr->Display();
getch();
}
void Rectangle::Display()
{
for (int W=1; W <= Width; W++)
{
for (int L=1; L <=Length; L++)
cout << '#';
cout << endl;
}
}
void Square::Display()
{
for (int W=1; W <= Side/2; W++)
{
for (int L=1; L <=Side; L++)
cout << '#';
cout << endl;
}
}
PROG3119.CPP OUTPUT
##################################################
##################################################
##################################################
##################################################
##################################################
##################################################
##################################################
##################################################
##################################################
##################################################
############
############
############
############
############
############
#
##
###
####
#####
######
#######
########
#########
##########
###########
############
#############
##############