Chapter XXXI

You might also like

Download as doc, pdf, or txt
Download as doc, pdf, or txt
You are on page 1of 39

Chapter XXXI

Focus on Object Oriented Programming


Friend, Static and Virtual Functions

Chapter XXXI Topics


31.1 Introduction

31.2 Friend Functions

31.3 Static Data Members and Member Functions

31.4 Virtual Functions

31.5 Pure Virtual Functions

Chapter XXXI Friend, Static and Virtual Functions 31.1


31.1 Introduction

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.

Essentially, in this chapter we are looking at three types of functions, friend


functions, static functions, and virtual functions. This group can certainly be
called advanced functions. I seriously doubt that you will find these functions
mentioned in any introductory OOP chapters. It is certainly possible to do some
serious object oriented programming without these functions. On the other hand,
there are a variety of unique situations that are easier handled with the aid of the
material in this chapter.

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.

APCS Examination Alert

This chapter can be considered optional because currently


friend functions, static functions and virtual functions
are not included in the APCS C++ subset to be tested
on the APCS Examination.

The chapter is included not just for enrichment, if the time


allows, but more to understand Object Oriented Programming.
The three main OOP topics, encapsulation, inheritance and
polymorphism is addressed by each one of the three
functions.

Chapter XXXI Friend, Static and Virtual Functions 31.2


31.2 Friend Functions

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

Chapter XXXI Friend, Static and Virtual Functions 31.3


There are 10 frogs

Program PROG3102.CPP removes the ShowCount function from the Frogs


class and makes it a non-member function. The public ShowCount can be called
in the main function body, but the program will not compile. Function
ShowCount does not have access to the private attribute, Count.

// 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 ShowCount(Frogs Temp)


{
cout << "There are " << Temp.Count << " frogs." << endl;
}

void main()
{
clrscr();
Frogs F;
F.PutCount(10);
ShowCount(F);
getch();
}

PROG3102.CPP HAS NO EXECUTION OUTPUT

Error PROG3102.CPP ::'Count' is not accessible

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

Chapter XXXI Friend, Static and Virtual Functions 31.4


function that does not compile. Well, have patience because I am setting the stage
for our upcoming friend functions. Check out the next program example.

// 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

There are 10 frogs

In this program function ShowCount is still a non-member function. A quick


peek at the syntax (note, no scope resolution operator) tells the story. However,
this program compiles and provides the desired output. Somehow access to the
private segment of the Frogs class has been granted. This access is provided
because ShowCount is declared as a friend function of the Frogs class. I assure
you that the clever choice of friend was not by anybody who had to teach high
school students.

Chapter XXXI Friend, Static and Virtual Functions 31.5


It is true that ShowCount is mentioned inside the Frogs class, but ShowCount is
definitely not a member function of Frogs. Magically, the special syntax of the
reserved word friend has granted special powers to this non-member function.

Accessing the Private Segment with a Friend Function

The private segment of a class is meant to prevent


access by any function declared outside the class.
Access of the private segment traditionally is provided
by public member functions of the same class.

There exists a special exception to the rule that non-member


functions cannot access the private segment of a class.

A non-member function declared as a friend of some


class, X, does have access to the private or protected
segment of class X. The prototype of the non-member
function needs to be inserted in class X, and preceded
by the reserved word friend.

Example:

friend void ShowCount(Frogs);

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.

Chapter XXXI Friend, Static and Virtual Functions 31.6


Executing this program presents no problem with compiling or at runtime. The
program operates smoothly.

// 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 Piggy::ShowSavings(apstring Name) const


{
cout << endl;
cout << Name << " has " << Savings << " dollars saved" << endl;
}

Piggy Piggy::operator+(const Piggy &RHS)


{
cout << endl;
cout << "BINARY OPERATOR+ FUNCTION" << endl;
Piggy Temp(RHS);
Temp.Savings += Savings;
return Temp;
}

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

Chapter XXXI Friend, Static and Virtual Functions 31.7


Tom has 800 dollars saved

Sue has 700 dollars saved

Larry has 0 dollars saved

BINARY OPERATOR+ FUNCTION

Larry has 1500 dollars saved


The point is slowly coming in focus. This time a small change will be made for
the next program. The previous program added savings of two objects of the
Piggy class. How about a situation where you add savings from one Piggy object
with some other amount of money. This new scenario is attempted in program
PROG3105.CPP, which does not even have the decency to compile properly.

// 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;
};

void Piggy::ShowSavings(apstring Name) const


{
cout << endl;
cout << Name << " has " << Savings
<< " dollars saved" << endl;
}

Piggy Piggy::operator+(const Piggy &LHS, int RHS)


{
cout << endl;
cout << "BINARY OPERATOR+ FUNCTION" << endl;
Piggy Temp(LHS);
Temp.Savings += RHS;
return Temp;
}

Chapter XXXI Friend, Static and Virtual Functions 31.8


void main()
{
clrscr();
Piggy Tom(800);
Piggy Larry(0);
Tom.ShowSavings("Tom");
Larry.ShowSavings("Larry");
Larry = Tom + 700;
Larry.ShowSavings("Larry");
getch();
}

PROG3105.CPP HAS NO OUTPUT EXECUTION

Error PROG2505.CPP 15: ’Piggy::operator+(const Piggy &, int)’


must be declared with one parameter

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:

Chapter XXXI Friend, Static and Virtual Functions 31.9


Piggy(int S) { Savings = S; }
void ShowSavings(apstring);
friend Piggy operator+(const Piggy &, int);
};

Piggy operator+(const Piggy &LHS, int RHS)


{
cout << endl;
cout << "BINARY OPERATOR+ FUNCTION" << endl;
Piggy Temp(LHS.Savings);
Temp.Savings += RHS;
return Temp;
}

void main()
{
clrscr();
Piggy Tom(800);
Piggy Larry(0);
Tom.ShowSavings("Tom");
Larry.ShowSavings("Larry");
Larry = Tom + 700;
Larry.ShowSavings("Larry");
getch();
}

void Piggy::ShowSavings(apstring Name)


{
cout << endl;
cout << Name << " has " << Savings
<< " dollars saved" << endl;
}

PROG3106.CPP OUTPUT

Tom has 800 dollars saved

Larry has 0 dollars saved

BINARY OPERATOR+ FUNCTION

Larry has 1500 dollars saved

Chapter XXXI Friend, Static and Virtual Functions 31.10


In the earlier "overloaded operator" chapter, friend functions were first
introduced. At that time you learned that it is quite possible to use free functions
that access private class data via the use of helper functions. The intention of this
chapter is not to advise on which method is better. The use of friend functions,
and its syntax is shown here. There are situations where the use of a friend
functions is a very good solution to certain access problems.

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:

Friend Function Warning

Encapsulation has been designed into Object Oriented


Programming to help promote information hiding and
program reliability. The aim is to write reliable programs that
do not allow accidental access and alteration of important,
private class member data.

Friend functions allow a programmer to circumvent the


protection access provided by private. There are times
when this exception may be considered worthwhile.

This section has been included to explain how friend


functions can solve special access situations.

Keep in mind that the use of friend functions is discouraged


by the College Board APCS development committee and
its use will not be tested on the APCS Examination.

Chapter XXXI Friend, Static and Virtual Functions 31.11


31.3 Static Data Members and
Static Member Functions

It is a natural assumption to think that the construction of three different objects of


the same class will result in three sets of different object attributes. The access of
one object’s attribute should not have an impact on any other object’s attributes.
This kind of assumption is illustrated by program PROG3107.CPP, which
defines three Aardvarks objects with three different attributes values. The
instantiation of the three objects is followed by three calls to the IncCount action,
which increments AardvarkCount. The output execution shows that the three
objects of the same class each access and control their respective data members.

// 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 IncrementAardvarks(Aardvarks &A)


{
A.IncCount();
cout << "Number #" << A.GetNumber() << " has "
<< A.GetCount() << " aardvarks." << endl;
}

void main()
{
clrscr();
Aardvarks A1(1,5);
Aardvarks A2(2,15);

Chapter XXXI Friend, Static and Virtual Functions 31.12


Aardvarks A3(3,25);
IncrementAardvarks(A1);
IncrementAardvarks(A2);
IncrementAardvarks(A3);
getch();
}

PROG3107.CPP OUTPUT

Number #1 has 6 aardvarks


Number #2 has 16 aardvarks
Number #3 has 26 aardvarks

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>

Chapter XXXI Friend, Static and Virtual Functions 31.13


class Aardvarks
{
private:
int Number;
static 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; }
};

int Aardvarks::AardvarkCount = 0; // initialize static attribute

void IncrementAardvarks(Aardvarks &A)


{
A.IncCount();
cout << "Number #" << A.GetNumber() << " has "
<< A.GetCount() << " aardvarks." << endl;
}

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

Number #1 has 26 aardvarks


Number #2 has 27 aardvarks
Number #3 has 28 aardvarks

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.

Chapter XXXI Friend, Static and Virtual Functions 31.14


Please realize that static does not mean constant here. The static is not about the
value of the static data member, which as you see, can change rather easily. The
static means that the number of copies of the data member does not change
because there is only one. It also means that access is available after the object is
destroyed. Somehow, the static data member is still hanging around; it has not
changed; it is static.

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++; }

Chapter XXXI Friend, Static and Virtual Functions 31.15


int GetCount() const
{ return AardvarkCount; }
};

int Aardvarks::AardvarkCount = 0; // initialize static attribute

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

Chapter XXXI Friend, Static and Virtual Functions 31.16


Constructor
8 aardvarks
Destructor

Constructor
9 aardvarks
Destructor

9 aardvarks

Static Data Members

A static data member represents the class and not the


objects defined as the same class. All defined objects of
the same class share the same static data member.

Static data members can be accessed, even after all


objects are no longer in scope.

Declaration example:

static int AardvarkCount;

Static attributes must be initialized “globally” after the


class declaration, such as:

int Aardvarks::AardvarkCount = 0;

Chapter XXXI Friend, Static and Virtual Functions 31.17


Access of static attributes is possible after all objects are
no longer in scope. Such access requires using the
class identifier and the scope resolution operator like:

cout << Aardvarks::AardvarkCount << endl;

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.

Program PROG3110.CPP repeats the performance of the previous program.


However, this time we properly leave AardvarkCount in its appropriate private
segment and access its information with the static action, Getcount.

// 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:

Chapter XXXI Friend, Static and Virtual Functions 31.18


Aardvarks()
{ cout << "Constructor" << endl; }
~Aardvarks()
{ cout << "Destructor" << endl << endl; }
void PutCount(int C)
{ AardvarkCount = C; }
void IncCount()
{ AardvarkCount++; }
static int GetCount()
{ return AardvarkCount; }
};

int Aardvarks::AardvarkCount = 0; // initialize static attribute

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

Chapter XXXI Friend, Static and Virtual Functions 31.19


6 aardvarks
Destructor

Constructor
7 aardvarks
Destructor

Constructor
8 aardvarks
Destructor

Constructor
9 aardvarks
Destructor

9 aardvarks

Static Member Functions

Static member functions can access static data members


during the scope of an object and also after the object no
longer exists.

Static member functions only access static data members.

Access of static data members, during the scope of some


object, is done in the same manner as most accessor
member functions, like:

N = A.GetCount();

Chapter XXXI Friend, Static and Virtual Functions 31.20


Access of static data members after an object no longer
exists requires using the class identifier and the scope
resolution operator in the following manner:

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++; }

Chapter XXXI Friend, Static and Virtual Functions 31.21


static int GetCount()
{ return AardvarkCount; }
};

int Aardvarks::AardvarkCount = 0; // initialize static attribute

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

Chapter XXXI Friend, Static and Virtual Functions 31.22


Creating Aardvark # 5
9 aardvarks
Destroying Aardvark # 5

9 aardvarks

31.4 Virtual Functions

One of the “big three” features of object oriented programming is polymorphism.


You have seen polymorphism with overloaded functions, overloaded operators,
and templates. These were three examples of “many forms” used in C++. We
have not discussed the notion that C++ supports polymorphisms at compile-time
and at run-time. Overloaded functions, overloaded operators, and templates are
compile-time polymorphisms. Compile-time means that the C++ determines at
compile-time which form of the identifier needs to be compiled into object code.
In the case of overloaded functions, the same identifier uses different parameters.
It is with proper parameter matching that the compiler decides which function
implementation needs to be used. A similar process occurs with overloaded
operators. Existing operators are given new meaning in a class declaration. C++
can identify by the class declaration which operator is standard C++ and which
operator is overloaded. Everything is neatly handled long before you try to
execute the program.
Templates may use generic function parameters and generic types in a class, but
at compile time, the specific data type can be determined by the nature of the
program. The template, which is generically written, actually becomes specific
for the appropriate data types during compile time.

Polymorphism Types

Polymorphism (many forms) is possible at compile-time,


and at run-time in C++.

Overloaded functions, overloaded operators, and templates

Chapter XXXI Friend, Static and Virtual Functions 31.23


are compile-time polymorphisms.

Run-time polymorphism is implemented with inheritance


and virtual functions.

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

Chapter XXXI Friend, Static and Virtual Functions 31.24


// scope resolution operator.

#include <iostream.h>
#include <conio.h>

class Level1
{
private:
public:
void Display() { cout << "Level1 Display" << endl; }
};

class Level2A : public Level1


{
private:
public:
void Display() { cout << "Level2A Display" << endl; }
};

class Level2B : public Level1


{
private:
public:
void Display() { cout << "Level2B 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.

Chapter XXXI Friend, Static and Virtual Functions 31.25


It is also possible to use the scope resolution operator. There is no Level1 object
defined, but it is still possible to access its Display function. With a statement
like cout << L2A.Level1::Display(); you can use an existing object like L2A and
then turn around and indicate that you really prefer another Display function.

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; }
};

class Level2A : public Level1


{
private:
public:
void Display() { cout << "Level2A Display" << endl; }
};

class Level2B : public Level1


{
private:
public:
void Display() { cout << "Level2B Display" << endl; }
};

Chapter XXXI Friend, Static and Virtual Functions 31.26


void main()
{
Level1 L1;
Level1 *Ptr;
Level2A L2A;
Level2B L2B;

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

Chapter XXXI Friend, Static and Virtual Functions 31.27


Level1 *Ptr; // line 2
Level2A L2A; // line 3
Level2B L2B; // line 4

clrscr();
Ptr = &L1; // line 5
Ptr->Display(); // line 6

Ptr = &L2A; // line 7


Ptr->Display(); // line 8

Ptr = &L2B; // line 9


Ptr->Display(); // line 10

getch();
}

Line 1 defines an object L1 of the Level1 class

Line 2 declares a pointer, Ptr to Level1 type

Line 3 defines an object, L2A, of the Level2A class

Line 4 defines an object, L2B, of the Level2B class

Line 5 stores the memory location of L1 in Ptr

Line 6 dereferences Ptr and calls the Display function

Line 7 stores the memory location of L2A in Ptr

Line 8 dereferences Ptr and calls the Display function

Line 9 stores the memory location of L2B in Ptr

Line 10 dereferences Ptr and calls the Display function


It is possible that you felt much time was wasted to explain the previous program.
After all, the previous program did not work at all. Well, time was not wasted
because we will use that very same pointer logic with one little step added. It
appears that the poor C++ compiler really is quite confused. We have inheritance
going on. We have multiple calls to same named functions happening, and all of
this with pointers thrown in for good measure.

Chapter XXXI Friend, Static and Virtual Functions 31.28


What comes to the rescue is the reserved word virtual. It is possible to declare a
base function as a virtual function. Without the use of pointers, a virtual function
would just sit back and behave like any other member function. However, this
changes considerably when virtual functions are accessed with pointers. This is
when run-time polymorphism becomes possible.

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; }
};

class Level2A : public Level1


{
private:
public:
void Display() { cout << "Level2A Display" << endl; }
};

class Level2B : public Level1


{
private:
public:
void Display() { cout << "Level2B Display" << endl; }
};

void main()
{
Level1 L1;
Level1 *Ptr;
Level2A L2A;
Level2B L2B;

clrscr();

Chapter XXXI Friend, Static and Virtual Functions 31.29


Ptr = &L1;
Ptr->Display();

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; }
};

class Level2 : public Level1


{
private:
public:
void Display() { cout << "Level2 Display" << endl; }
};

class Level3 : public Level2


{
private:
public:
void Display() { cout << "Level3 Display" << endl; }

Chapter XXXI Friend, Static and Virtual Functions 31.30


};

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.

Chapter XXXI Friend, Static and Virtual Functions 31.31


#include <iostream.h>
#include <conio.h>

class Level1
{
private:
public:
virtual void Display() { cout << "Level1 Display" << endl; }
};

class Level2 : public Level1


{
private:
public:
void Display() { cout << "Level2 Display" << endl; }
};

class Level3 : public Level2


{
private:
public:
// no display function
};

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

Chapter XXXI Friend, Static and Virtual Functions 31.32


Program PROG3117.CPP repeats the “what if no function exists” question, but
goes one more level. In this case you will see that three calls are made to the
highest level Display function.

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; }
};

class Level2 : public Level1


{
private:
public:
// no display function
};

class Level3 : public Level2


{
private:
public:
// no display function
};

void main()
{
Level1 L1;
Level1 *Ptr;
Level2 L2;
Level3 L3;
clrscr();
Ptr = &L1;
Ptr->Display();
Ptr = &L2;
Ptr->Display();
Ptr = &L3;
Ptr->Display();

Chapter XXXI Friend, Static and Virtual Functions 31.33


getch();
}
PROG3117.CPP OUTPUT

Level1 Display
Level1 Display
Level1 Display

31.5 Pure Virtual Functions

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.

Pure Virtual Functions

A virtual functions without any executable code is called


a pure virtual function. Special syntax is required to
identify the function as pure.

Example:

virtual void Display() = 0;

A class which uses at least one pure virtual function is


called an abstract class.

Chapter XXXI Friend, Static and Virtual Functions 31.34


Program PROG3118.CPP shows how to use a pure virtual function. You will
see that the Level1 base class serves little purpose, except for being an abstract
class with a function that can be re-defined at a later stage.

// 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;
};

class Level2 : public Level1


{
private:
public:
void Display() { cout << "Level2 Display" << endl; }
};

class Level3 : public Level2


{
private:
public:
void Display() { cout << "Level3 Display" << endl; }
};

void main()
{
Level1 *Ptr;
Level2 L2;
Level3 L3;

clrscr();

Ptr = &L2;
Ptr->Display();

Ptr = &L3;
Ptr->Display();

getch();
}

PROG3118.CPP OUTPUT

Chapter XXXI Friend, Static and Virtual Functions 31.35


Level2 Display
Level3 Display

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;
};

class Rectangle : public Abstract


{
private:
int Length,Width;
public:
Rectangle(int L, int W) { Length=L; Width=W; }
void Display();
};

class Square : public Abstract


{
private:
int Side;
public:
Square(int S) { Side=S; }
void Display();
};

class Triangle : public Abstract


{

Chapter XXXI Friend, Static and Virtual Functions 31.36


private:
int Base;
public:
Triangle(int B) { Base=B; }
void Display();
};

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;
}
}

Chapter XXXI Friend, Static and Virtual Functions 31.37


void Triangle::Display()
{
for (int H=1; H <= Base; H++)
{
for (int B=1; B <=H; B++)
cout << '#';
cout << endl;
}
}

PROG3119.CPP OUTPUT

##################################################
##################################################
##################################################
##################################################
##################################################
##################################################
##################################################
##################################################
##################################################
##################################################

############
############
############
############
############
############

#
##
###
####
#####
######
#######
########
#########
##########
###########
############
#############
##############

Chapter XXXI Friend, Static and Virtual Functions 31.38


###############

Chapter XXXI Friend, Static and Virtual Functions 31.39

You might also like