Programming Workshop 2 (CSCI 1061U)
Faculty of Science, Ontario Tech University
Polymorphism is a key principle of object-oriented programming. It enables us to assocaite many meanings to a single function. Virtual functions provide this capability.
Consider the following shapes.h
and
shapes.cpp
files that implement three classes:
Shape
, Circle
, and Rectangle
.
Classes Circle
and Rectangle
are inherited
from class Shape
.
shapes.h
class Shape {
public:
();
Shape~Shape();
void draw(); // draws an empty shape
};
class Rectangle : public Shape
{
public:
(int w, int h);
Rectangle~Rectangle();
void draw(); // draws a rectangle
private:
int _w, _h; // width & height
};
class Circle : public Shape
{
public:
(int r);
Circle~Circle();
void draw(); // draws a circle
private:
int _r; // radius
};
shapes.cpp
#include "Shapes.h"
#include <iostream>
using namespace std;
::Shape() {}
Shape
::~Shape() {}
Shape
void Shape::draw()
{
<< "Drawing an empty shape" << endl;
cout }
/////////////////////////////////////////////
::Rectangle(int w, int h)
Rectangle: _w(w), _h(h)
{}
::~Rectangle() {}
Rectangle
void Rectangle::draw()
{
<< "Drawing a rectangle of width " << _w
cout << " and height " << _h
<< endl;
}
/////////////////////////////////////////////
::Circle(int r)
Circle: _r(r)
{}
::~Circle() {}
Circle
void Circle::draw()
{
<< "Drawing a circle of radius " << _r << endl;
cout }
Now lets consider the following main.cpp
file.
main.cpp
#include "Shapes.h"
int main()
{
* c = new Circle(2);
Circle->draw(); // this draws circle
c
* r = new Rectangle(3,6);
Rectangle->draw(); // this draws rectangle
r
delete c; delete r;
}
If we compile and run this program we get
> g++ main.cpp shapes.cpp -o ex1
> ./ex1
Drawing a circle of radius 2
Drawing a rectangle of width 3 and height 6
This is as we expected.
Now lets consider another situation. Consider the following program that stores a list of shapes in an array.
main2.cpp
#include "Shapes.h"
int main()
{
* s[2]; // a list of different kind of shapes
Shape[0] = new Circle(4);
s[1] = new Rectangle(4,5);
s
for (int i=0; i<2; ++i) {
[i]->draw();
s}
for (int i=0; i<2; ++i) {
delete s[i];
}
}
We know that an array only store elements of the same type. The array
s
in the above code stores pointers to Shape
.
We assign a pointer to a Circle
instance to
s[0]
and a pointer to a Rectangle
instance to
s[1]
. This is allowed since both Circle
and
Rectangle
are subclasses of Shape
. Recall that
Circle
is a Shape
, and
Rectangle
is a Shape
.
However, when we compile and run the above program, we get the following
> g++ main2.cpp shapes.cpp -o ex2
> ./ex2
Drawing an empty shape
Drawing an empty shape
It seems that the s[i]->draw()
isn’t behaving as we
expected. Obviously, the draw
function in class
Shape
is being called. We want the draw
functions of the respective classes to be called.
s[0]->draw()
should call the draw
function
in Circle
class. Similarly s[1]->draw()
should call the draw
function in Rectangle
class. How do we do it?
We can use late binding to discern the actual type of the
pointer and then call the appropriate function. So, we can discern that
s[0]
is actually a pointer to a Circle
instance. In C++, we achieve this be declaring a function
virtual. Specifically, we will modify the parent class as
follows
class Shape {
public:
();
Shape~Shape();
virtual void draw(); // draws an empty shape
};
By declaring the draw
function virtual in the
parent class, we assert that function implementation is not known at
compile time. Furthermore, that function implementation should be
inferred from object instance at runtime. Note that in C++ late binding
is performed only for functions declared virtual.
If we compile and execute the program now, we get the correct behavior
> g++ main2.cpp shapes.cpp -o ex2
> ./ex2
Drawing a circle of radius 4
Drawing a rectangle of width 4 and height 5
If virtual function definition is changed in the derived class, we say that it has been overridden. When a non-virtual function is changed in the derived class, we say that the function has been redefined.
C++11 includes override
keyword that makes it clear
whether a function overrides a function in the parent class.
class Sale:
{
public:
virtual double bill() const;
};
class DiscountSale: public Sale
{
public:
double bill() const override;
};
final
keywordC++11 includes the final keyword to prevent a function from being overridden. Useful if a function is overridden but don’t want a derived classes to override it again.
class Sale:
{
public:
virtual double bill() const final;
};
class DiscountSale: public Sale
{
public:
double bill() const; // This will give a compiler error
};
Base classes may not have a meaningful definitions for some of its functions. The purpose of the base class is solely for others to derive from it. It is possible to create a pure virtual function as follows
class Shape
{
public:
virtual draw() = 0; // Pure virtual function
};
This has two consquences. First, it is not possible to create an
instance of Shape
class. Shape
class is now an
abstract class. We cannot create an instance of Shape
class, since it is missing definition of one of its functions. Second,
it a derived (from Shape
) class must implement
draw
. If the derived class doesn’t implmenet
draw
the the derived class is also an abstract class.
Virtual functions play an important role; however, they do come with an overhead. These use more storage. The programs also run slower due to late binding, which is performed on the fly. Only use these when needed.
When in doubt make destructors virtual. This would prevent memory leaks.