# Polymorphism

Consider an example where a class Tank and a class SelfPropelledGun inherit from a class WarMachine.

Now when creating objects on the stack and not using them via baseclass pointers, we would be able to write the following code:

WarMachine machine("A small rescue jeep used in world war 2 in good shape.", 510000);
WarMachine tank("A Tiger 1 tank prototype in bad shape.", 299250);
WarMachine spg("An ISU-152 SPG in non-working condition. Needs revision.", 2000000);

// ...

cout << machine.to_string() << endl;
cout << tank.to_string() << endl;
cout << spg.to_string() << endl;
1
2
3
4
5
6
7
8
9

In this case the output would be:

Output

WarMachine: A small rescue jeep used in world war 2 in good shape. Price = 510'000 euro
Tank: A Tiger 1 tank prototype in bad shape. Price 299'250 euro
SPG: An ISU-152 SPG in non-working condition. Needs revision. Price = 2'000'000 euro

Polymorphism comes from Greek and means:

  • Poly = many
  • Morph = form, shape

So polymorphism is the ability of an object to take on many forms. The most common use of polymorphism in OOP occurs when a parent class reference is used to refer to a child class object.

This basically means that you can do the following in our WarMachine example application:

WarMachine * machine = new WarMachine("A small rescue jeep used in world war 2 in good shape.", 510000);
WarMachine * tank = new Tank("A Tiger 1 tank prototype in bad shape.", 299250);
WarMachine * spg = new SelfPropelledGun("An ISU-152 SPG in non-working condition. Needs revision.", 2000000);
1
2
3

Do take note that this only works when using pointers of the baseclass type. We cannot do this when creating local variables on the stack unless we then access them via a pointer of the baseclass as shown below:

Tank tank;

WarMachine * machine = &tank;
1
2
3

This is often used when storing subtypes inside an array or container class:

std::vector<WarMachine*> machines;

machines.push_back(new WarMachine("A small rescue jeep used in world war 2 in good shape.", 510000));
machines.push_back(new Tank("A Tiger 1 tank prototype in bad shape.", 299250));
machines.push_back(new SelfPropelledGun("An ISU-152 SPG in non-working condition. Needs revision.", 2000000));
1
2
3
4
5

This allows all the superclass and subclass instances to be store together in a list. If this were not possible it would be necessary to create separate lists for each type.

Of course in a realistic application the list would be populated from a database or a file.

C++ tracks the actually type of the objects. This basically means that while all the objects created above are WarMachines because of inheritance, C++ still knows that some are Tanks or SPGs.

Polymorphism allows us to store subtypes inside an array of the baseclass type. Now what would happen if we were to add the following code to the application:

for (unsigned int i = 0; i < machines.size(); i++) {
  cout << machines[i]->to_string() << endl;
}
1
2
3

Which would output:

Output

WarMachine: A small rescue jeep used in world war 2 in good shape. Price = 510'000 euro
WarMachine: A Tiger 1 tank prototype in bad shape. Price 299'250 euro
WarMachine: An ISU-152 SPG in non-working condition. Needs revision. Price = 2'000'000 euro

This is actually not what we expected. We expected that each WarMachine item's 'correct' to_string() method would be called.

Important to know is that while method overriding can be done out of the box, polymorphism needs to be enabled in C++ and is default not. A method can be declared a candidate for late binding (polymorphism) by appending the keyword virtual before the declaration of the method in the class as shown below. Strictly speaking only the to_string() method of WarMachine needs to be declared virtual here.

class WarMachine {
  // ...
  public:
    virtual std::string to_string(void);
};
1
2
3
4
5
class Tank : public WarMachine {
  // ...
  public:
    std::string to_string(void);
};
1
2
3
4
5

If the same main code is executed again the output will be:

Output

WarMachine: A small rescue jeep used in world war 2 in good shape. Price = 510'000 euro
Tank: A Tiger 1 tank prototype in bad shape. Price 299'250 euro
SPG: An ISU-152 SPG in non-working condition. Needs revision. Price = 2'000'000 euro

# Another look at polymorphism

Source: http://stackoverflow.com/questions/2391679/why-do-we-need-virtual-functions-in-c#comment32597274_2392656 (opens new window)

Let's say you have these two classes:

class Animal {
  public:
    void eat(void) {
      std::cout << "I'm eating generic food.";
    }
};
1
2
3
4
5
6
class Cat : public Animal {
  public:
    void eat(void) {
      std::cout << "I'm eating a rat.";
    }
};
1
2
3
4
5
6

In your main function:

void main(void) {
  Animal * animal = new Animal();
  Cat * cat = new Cat();

  animal->eat(); // outputs: "I'm eating generic food."
  cat->eat();    // outputs: "I'm eating a rat."
}
1
2
3
4
5
6
7

So far so good right? Animals eat generic food, cats eat rats, all without virtual.

Let's change it a little now so that eat() is called via an intermediate function (a trivial function just for this example):


void make_it_eat(Animal * animal) {
  animal->eat();
}

void main(void) {
  Animal * animal = new Animal();
  Cat * cat = new Cat();

  make_it_eat(animal);  // outputs: "I'm eating generic food."
  make_it_eat(cat);     // outputs: "I'm eating generic food."
}
1
2
3
4
5
6
7
8
9
10
11
12

Uh oh ... we passed a Cat into make_it_eat(), but it won't eat rats. Should you overload make_it_eat() so it takes a Cat * ? If you have to derive more animals from Animal they would all need their own make_it_eat().

The solution is to make eat() a virtual function:

class Animal {
  public:
    virtual void eat(void) {
      std::cout << "I'm eating generic food.";
    }
};
1
2
3
4
5
6

Where now all goes well:


void make_it_eat(Animal * animal) {
  animal->eat();
}

void main(void) {
  Animal * animal = new Animal();
  Cat * cat = new Cat();

  make_it_eat(animal);  // outputs: "I'm eating generic food."
  make_it_eat(cat);     // outputs: "I'm eating a rat."
}
1
2
3
4
5
6
7
8
9
10
11
12

If we did not have this polymorphic behavior we would have to create a make_it_eat() method for each type of Animal. This would definitely cause lots of errors and headaches.

So polymorphism is again another technique that allows us to write short, clean and maintainable code.

# Virtual Destructors

The virtual keyword is not only important for methods we which to override and access via baseclass pointers. It is also important when considering dynamic memory usage. When reserving memory in your objects, you need to free it once the objects are destroyed. This is accomplished using the delete keyword. This has been discussed in detail in the chapter "Memory Allocation".

However what if you extend this class. Is the destructor of the baseclass still called in that case?

Let's take a look at a simple example:

class Animal{
  public:
    ~Animal(){
        std::cout << "Destroying an Animal" << std::endl;
    }
};
1
2
3
4
5
6

and a subclass from Animal named Cat:

class Cat : public Animal {
  public:
    ~Cat(){
        std::cout << "Destroying a Cat" << std::endl;
    }
};
1
2
3
4
5
6

If we create a new Cat we expect both destructors to be called:

int main() {
    Animal * a = new Cat();
    delete a;
    return 0;
}
1
2
3
4
5

However the only thing the output shows is:

Output

Destroying an Animal

Do note that this is not the case when a Cat object is created on the stack.

To fix this, we are required to make the destructor of Animal virtual.

class Animal{
  public:
    virtual ~Animal(){
        std::cout << "Destroying an Animal" << std::endl;
    }
};
1
2
3
4
5
6
int main() {
    Animal * a = new Cat();
    delete a;
    return 0;
}
1
2
3
4
5

Rendering the output

Output

Destroying a Cat
Destroying an Animal

Note that the destructors are executed in the opposite order as the constructors.

Last Updated: 12/2/2020, 7:23:02 AM