Polymorphism - Virtual Functions and Abstract Classes

In the last lab we talked about inheritance.  We learned that a "child" class can inherit the members of a "base" class and receive its functionality.  We were dealing with what is called concrete classes.  A concrete class is simply a class that you can create an instance of.  When we talk about polymorphism we are talking about abstract classes.  Abstract classes are not like concrete classes.  You cannot create an instance of an abstract class.  Abstract classes are used as interfaces.  They help to define how child classes should behave.

For example, we know what an animal is.  We know that animals have legs, weight and height.  But, we can't just have an animal.  It has to be some type of animal.  Here is the definition of an abstract animal class.  The I in front of Animal is just a naming convention and means the class is an Interface.

#include <string>

class IAnimal
{
public:
       IAnimal(std::string n);
       virtual ~IAnimal();

       virtual void PrintName() = NULL;

protected:
       int number_of_legs;
       int height;
       int weight;
       std::string name;
};

The first thing you will probably notice is a new keyword: virtual.  Notice that PrintName() is set equal to NULL.  This makes the function a pure virtual function.  If a class contains a pure virtual function, that class becomes an abstract class.  A pure virtual function is not implemented by the abstract class, it is up to the derived classes to implement the function.  Here is the implementation of IAnimal:

#include "IAnimal.h"
#include <iostream>

using namespace std;

IAnimal::IAnimal(string n)
{
       name = n;

       cout << "Animal created." << endl;
}

IAnimal::~IAnimal()
{
       cout << "Animal destroyed." << endl;
}

Note that PrintName is not implemented because it is pure virtual, but the destructor is implemented even though it is virtual.  If you do not set a virtual function to NULL you will have to implement it in that class or the compiler will complain.  In other words, implement virtual functions in the base class, but do not implement pure virtual functions in the base class.

Ok, so why the heck would you declare a function that you cannot use?  First, we will create a Dog class that inherits from IAnimal (class CDog has a C because it is a concrete class, another naming convention):

#include "IAnimal.h"

class CDog:public IAnimal
{
public:
       CDog(std::string n);
       ~CDog();

       void Bark();
       void PrintName();
};

We see that CDog inherits from IAnimal.  CDog does not use the virtual keyword because it is going to actually implement the function that was virtual in IAnimal (remember PrintName()?).

#include "IAnimal.h"

class CDog:public IAnimal
{
public:
       CDog(std::string n);
       ~CDog();

       void Bark();
       void PrintName();
};

Now let's see the implementation of CDog:

#include "CDog.h"
#include <iostream>

using namespace std;

CDog::CDog(string n):IAnimal(n)
{
       cout << "Dog created." << endl;
}

CDog::~CDog()
{
       cout << "Dog destroyed." << endl;
}

void CDog::Bark()
{
       cout << "woof!" << endl;
}

void CDog::PrintName()
{
       cout << "My name is " << name << endl;
}

Just your standard class implementation, you have seen this many times before.  You may create a pointer to a derived class object and assign it an abstract class pointer.  When a virtual function is called using this pointer, the correct implementation of that function will be used depending on what type of object the pointer is really pointing to.  That's a lot to take in so let's look at an example:

#include "CDog.h"
#include <iostream>

void main(void)
{
       IAnimal* dog = new CDog("Bob");

       dog->PrintName();

       delete dog;
}

dog is a pointer to an IAnimal.  We assign a pointer to a CDog object to dog.  dog is now an IAnimal pointer that points to a CDog.  When PrintName() is called by dog, the correct implementation is found.  The output should be:

Animal created.
Dog created.
My name is Bob
Dog destroyed.
Animal destroyed.

We can create more derived classes from IAnimal and create instances in the same way and the correct implementation of PrintName() will always be found.  Pretty cool huh?  That is what polymorphism is all about, you create the interface and then the implementation can take on many forms.

One final note.  I did not mention before why ~IAnimal (IAnimal's destructor) has the virtual keyword in front of it.  If you do not make the destructor of an abstract class virtual, the destructor for the derived class will not be called.  This will cause some nasty memory leaks.  Let's modify the definition of IAnimal and test this:

#include <string>

class IAnimal
{
public:
       IAnimal(std::string n);
       ~IAnimal();    //note that the virtual keyword has been removed

       virtual void PrintName() = NULL;

protected:
       int number_of_legs;
       int height;
       int weight;
       std::string name;
};

Now when we run this the output is:

Animal created.
Dog created.
My name is Bob
Animal destroyed.

Notice that the destructor for CDog is never called.  This is bad, so just remember to make the destructor of any abstract class virtual.