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 <<
}
/////////////////////////////////////////////
int w, int h)
Rectangle::Rectangle(
: _w(w), _h(h)
{}
Rectangle::~Rectangle() {}
void Rectangle::draw()
{"Drawing a rectangle of width " << _w
cout << " and height " << _h
<<
<< endl;
}
/////////////////////////////////////////////
int r)
Circle::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()
{new Circle(2);
Circle* c = // this draws circle
c->draw();
new Rectangle(3,6);
Rectangle* r = // this draws rectangle
r->draw();
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()
{2]; // a list of different kind of shapes
Shape* s[0] = new Circle(4);
s[1] = new Rectangle(4,5);
s[
for (int i=0; i<2; ++i) {
s[i]->draw();
}
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.