·
Polymorphism –
refers to the ability to associate many meanings to one function name by means
of virtual functions and late binding.
Thus, polymorphism, virtual functions, and late binding are all the same
topic.
o
The ability to
assign an address of an object of a derived class to a pointer to a base class
is the essence of polymorphism.
o
Virtual Functions
– A virtual function is should be used when you do not know how the function is
to be implemented. The compiler will
wait until the function is used in a program and then get the implementation
from the object instance. Virtual
functions are the way that C++ provides late binding.
§
Late Binding –
When a function call is resolved at run time
§
Early Binding –
When a function call is resolved at compile time.
o
How Virtual
Member Functions Work – Suppose we have a Mammal class. When an object derived from the Mammal class (e.g.,
a Dog object) is created, the constructor for the Mammal class is called first
and then the constructor for the Dog class is called. The memory for the Mammal part of the object
is contiguous in memory with the Dog part.
The Dog object would look as follows:

When a virtual function is created in an object, the
object must keep track of that function.
Compilers build a virtual function table (v-table) to do just that. A v-table is kept for each type, and each
object of that type keeps a virtual table pointer (v-pointer), which points to
that table. Each object’s v-pointer
points to the v-table that, in turn, has a pointer to each of the virtual member
functions. Thus, when the Mammal part of
the Dog is created, the v-pointer is initialized to point to the v-table for
the Mammal class.

When the Dog constructor is called and the dog part of
the object is added, the v-pointer is adjusted to point to the virtual function
overrides (if any) in the Dog object. If
the Speak() method is overridden in the Dog class,
then the pointer is adjusted as shown below:

When a pointer to Mammal is used, the v-pointer
continues to point to the correct function, depending on the real type of the
object. Thus, when Speak()
is invoked, the correct function is invoked.
If Speak() is virtual and overridden, the
implementation from Dog is invoked. If Speak() is not virtual or is virtual and not overridden,
then the implementation from Mammal is invoked.
//
Example
#include
<iostream>
using namespace std;
class Mammal
{
public:
Mammal() : _itsAge(1) { cout << “Mammal
Constructor.\n”; }
virtual ~Mammal()
{ cout << “Mammal Destructor.\n”; }
void Move() const
{
cout << “Mammal moves one step forward.\n”;
}
virtual void
Speak() const
{
cout << “Mammal Speak!\n”;
}
protected:
int
_itsAge;
};
class Dog : public Mammal
{
public:
Dog() { cout << “Dog Constructor.\n”; }
virtual ~Dog() { cout << “Dog Destructor.\n”; }
void WagTail() const
{
cout << “Wagging Tail.\n”;
}
void Move() const
{
cout << “Dog moves 4 steps.\n”;
}
virtual void
Speak() const
{
cout << “Bark!\n”;
}
};
int main()
{
cout
<< “\nOutput using a Mammal Pointer\n”;
Mammal *pDog =
new Dog;
/*
* Move() is not a virtual function. Therefore, the Move()
* function that was
defined for Mammal is invoked.
*/
pDog->Move();
/*
* Speak() is a virtual function. Therefore, the v-table
*
will point to the overridden Speak() function that was
defined
* for Dog.
*/
pDog->Speak();
/*
*
Below produces a compiler error because ‘WagTail’ is
not a
* member of ‘Mammal’. This
is known as the slicing problem. This
* can be
avoided by casting the pointer.
*/
// pDog->WagTail();
// The following
two lines of code are legal casts of the pointer.
(static_cast<Dog
*>(pDog))->WagTail();
((Dog *)pDog)->WagTail();
delete pDog;
cout
<< “\nOutput using a Dog Pointer\n”;
Dog *pDog2 = new Dog;
pDog2->Move();
pDog2->Speak();
pDog2->WagTail();
delete pDog2;
return 0;
}
//
Output
Output using a Mammal Pointer
Mammal Constructor.
Dog Constructor.
Mammal moves one step forward.
Bark!
Wagging Tail.
Wagging Tail.
Dog Destructor.
Mammal Destructor.
Output using a Dog Pointer
Mammal Constructor.
Dog Constructor.
Dog moves 4 steps.
Bark!
Wagging Tail.
Dog Destructor.
Mammal Destructor.
Press any key to continue
o
Some Tidbits
about Virtual Functions
§
Virtual Functions
only operate on pointers and references.
Passing an object by value will not enable virtual member functions to
be invoked.
§
If any of the
functions in a class are virtual, the destructor
should be virtual as well. Why? Suppose we have a base pointer that points to
a dynamically allocated derived object. What happens when the pointer to the derived object
is deleted? If the destructor is virtual, the derived class’s destructor is called (as it
should be). The derived class’s
destructor will then automatically invoke the base class’s destructor, and the
entire object will be properly destroyed.
If the destructor were not virtual, then only
the base class destructor would be called (Bad!).
§
Overriding – When
a virtual function is changed in a derived class, the function definition is
overridden. If a non-virtual function is
changed in a derived class, the function is redefined. Both cases are treated differently by the
compiler.
o
Abstract Classes
and Pure Virtual Functions
§
Pure Virtual
Functions are used for situations in which you want to have a class to use as a
base class for a number of other classes, but you do not have any meaningful
definition to give to one or more of its member functions. For example, a Shape class might have a pure
virtual Draw() function that allows derived objects of
various shapes to draw shapes particular to their type.
§
An Abstract Class
is a class that has one or more pure virtual functions. A class that derives from an abstract class
that does not define inherited pure virtual functions, or has pure virtual
functions of its own, is also an abstract class.
·
It is not
possible to instantiate an object of an abstract class. Trying to do so will cause a compiler error.
§
Pure Virtual
Functions are virtual functions that are initialized to zero within a class
definition. A class with one or more
pure virtual functions is an abstract class.
The pure virtual function must be overridden in a derived class or else
the derived class is also an abstract class.
This logic carries through a class hierarchy until a definition of the
pure virtual function is provided. Thus,
pure virtual functions force the programmer to write a definition in a derived
class.
// Example
#include
<iostream>
#include
<cmath>
using namespace std;
//
Shape
class Shape
{
public:
Shape();
virtual ~Shape();
virtual long GetArea() const = 0;
virtual long GetPerim() const = 0;
virtual void
Draw() const = 0;
};
Shape::Shape()
{
cout
<< "Shape Constructor.\n";
}
Shape::~Shape()
{
cout
<< "Shape Destructor.\n";
}
//
Circle
class Circle : public Shape
{
public:
Circle(int radius);
virtual
~Circle();
virtual long GetArea() const;
virtual long GetPerim() const;
virtual void
Draw() const;
private:
int
_radius;
};
Circle::Circle(int radius) : _radius(radius)
{
cout
<< "Circle Constructor.\n";
}
Circle::~Circle()
{
cout
<< "Circle Destructor.\n";
}
long Circle::GetArea() const
{
return (3 * pow(_radius, 2));
}
long Circle::GetPerim() const
{
return (2 * 3 *
_radius);
}
void Circle::Draw() const
{
cout
<< "Draw Circle Stub Function\n";
}
//
Rectangle
class Rectangle : public Shape
{
public:
Rectangle(int length, int width);
virtual
~Rectangle();
virtual long GetArea() const;
virtual long GetPerim() const;
virtual int GetLength() const;
virtual int GetWidth() const;
virtual void
Draw() const;
private:
int
_length;
int
_width;
};
Rectangle::Rectangle(int length, int
width)
: _length(length),
_width(width)
{
cout
<< "Rectangle Constructor.\n";
}
Rectangle::~Rectangle()
{
cout
<< "Rectangle Destructor.\n";
}
long Rectangle::GetArea() const
{
return (_length *
_width);
}
long Rectangle::GetPerim() const
{
return ((2 *
_length) + (2 * _width));
}
int Rectangle::GetLength()
const
{
return _length;
}
int Rectangle::GetWidth()
const
{
return _width;
}
void Rectangle::Draw() const
{
for(int i = 0; i
< _length; i++)
{
for(int j = 0; j < _width; j++)
cout << "* ";
cout << endl;
}
}
//
Square
class Square : public Rectangle
{
public:
Square(int length);
Square(int length, int width);
~Square();
virtual long GetPerim() const;
};
Square::Square(int length) : Rectangle(length,
length)
{
cout
<< "Square Constructor.\n";
}
Square::Square(int length, int
width) :
Rectangle(length,
width)
{
cout
<< "Square Constructor.\n";
if(GetLength() != GetWidth())
cout << "Error: Not a square!\n";
}
Square::~Square()
{
cout
<< "Square Destructor.\n";
}
long Square::GetPerim() const
{
return (4 * GetLength());
}
//
Main
int main()
{
int
choice;
bool
quit = false;
Shape *sPtr;
while(1)
{
do
{
cout << "(1)Circle (2)Rectangle ";
cout << "(3)Square (0)Quit: ";
cin >> choice;
switch(choice)
{
case
0:
quit
= true;
break;
case
1:
sPtr = new Circle(5);
break;
case
2:
sPtr = new Rectangle(4,7);
break;
case
3:
sPtr = new Square(5);
break;
}
} while((choice
< 0) || (choice > 3));
if(quit)
break;
sPtr->Draw();
delete sPtr;
cout << endl;
}
return 0;
}
//
Output
(1)Circle (2)Rectangle
(3)Square (0)Quit: 1
Shape Constructor.
Circle Constructor.
Draw Circle Stub Function
Circle Destructor.
Shape Destructor.
(1)Circle (2)Rectangle
(3)Square (0)Quit: 2
Shape Constructor.
Rectangle Constructor.
* * * * * * *
* * * * * * *
* * * * * * *
* * * * * * *
Rectangle Destructor.
Shape Destructor.
(1)Circle (2)Rectangle
(3)Square (0)Quit: 3
Shape Constructor.
Rectangle Constructor.
Square Constructor.
* * * * *
* * * * *
* * * * *
* * * * *
* * * * *
Square Destructor.
Rectangle Destructor.
Shape Destructor.
(1)Circle (2)Rectangle
(3)Square (0)Quit: 4
(1)Circle (2)Rectangle
(3)Square (0)Quit: -1
(1)Circle (2)Rectangle
(3)Square (0)Quit: 2
Shape Constructor.
Rectangle Constructor.
* * * * * * *
* * * * * * *
* * * * * * *
* * * * * * *
Rectangle Destructor.
Shape Destructor.
(1)Circle (2)Rectangle
(3)Square (0)Quit: 0
Press any key to continue