C++: Polymorphism
Polymorphism simply means "the occurrence of something in different forms". In C++, this usually refers to being able to access different types of objects through a common base class - specifically using a pointer of the type of a base object to point to object(s) which derive from that base class. If this doesn't make sense to you, it really isn't that difficult to understand - if classes 'B' and 'C' inherit from class 'A', a pointer which points to type class 'A' can also point to classes 'B' and 'C':
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
|
We can access the same object in multiple forms - hence polymorphism. Using the base class pointer in this way, however, means you cannot use member functions or member variables specific to the derived classes. This also means that with "regular" usage, if a derived class re-writes a function present in the base class, which would usually result in the base version being hidden and the derived version being used, the base version will instead be used. This is shown in the following snippet where the base version of "output" is used:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
|
Treating objects of different types in the same way is pretty useful in itself - just looping through an array of base class object pointers, for example, which actually point to objects of derived types. The way to overcome the derived member function "re-write" issue, however, is really where polymorphism starts to become even more useful.
Functions which are defined in classes can be defined using the virtual
modifier. This means that the behavior of the function can be overridden by derived classes, and thus when a base class pointer points towards a derived object and uses a virtual member function on the object it's pointing to which the derived class has over-written, the derived version will be used instead of the base one! The easiest way to show this would be to provide an example. The classic example is that a base "Shape" class has two derived classes: "Circle" and "Rect". First, let's set everything up without using the virtual keyword to see what goes wrong:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
|
In the above example, both "area_calc" calls return '0'. This clearly isn't the intended behaviour as both derived classes implement their own versions of the "area_calc" function, and as such we can solve this problem by making the "area_calc" in the base class virtual
to show that the derived versions should be used if present. With this modification, the application performs as expected:
1 2 3 4 5 6 |
|
This behaviour is fairly useful in a variety of circumstances and the result is essentially that derived class objects can be handled via a base class pointer (or an array of base class pointers), making it very easy to handle many derived classes, and also that member function implementations specific to derived objects can still be utilized. I should probably add here, that when an inheritance relationship is set up - the base class should almost always have a virtual
destructor. The reason for this is that if a derived class object is used via a base class pointer, the base class destructor will be used when the object is destroyed through the pointer, and thus if the derived class has something important to do in its destructor (like deleting allocated memory), then this is not executed (and hence a memory leak occurs where the allocated memory is not deleted - this is a very bad thing).
But going back to out 'Shape' class - there isn't a situation in which we actually want a real object of the base class to be declared. As such, the base class simply provides a basis and a structure for classes which inherit from it, and so it is known as an abstract class. To let our compiler know that we never want to create objects of type "Shape", and to fix the rather lackluster "return 0;" functionality in our base "area_calc" class, we can make what is called a "pure" virtual member function.
Any class with a pure virtual member function (often called an abstract function) cannot be used to create objects (although pointers are still fine!), and furthermore, any derived class that doesn't implement a parent's pure virtual function is also defined as an abstract/pure class and cannot be used to create objects either! In our case, this is perfect - anything that derives from "Shape" should define an "area_calc" function to calculate the area of that shape. Pure member functions are defined by using the virtual
function modifier, and assigning the function the value 0:
1 2 3 4 5 6 |
|
One more thing to note before we tie off this tutorial is that like most function modifiers, and like using default parameters, you only need to write the thing once. Either in the interface/structure, or in the implementation - in the case of the virtual keyword, if you're structuring and implementing your function separately (i.e. using separate header and source files), you probably want to use the virtual
keyword in your header class structure, but not in your actual source file implementation. This way it will be easily visible when viewing the general structure and should be easy to change if necessary.
To tie off this tutorial, try creating a practical application which makes use of abstract classes and polymorphism. Think of something general, for example an abstract "Weapon" class in a primitive game, and then make a few classes which inherit from this, and make use of an array of base class pointers to manipulate the derived objects in a dynamic way. It might even be a good idea to dynamically allocate some memory to make things even more dynamic for practice, although make sure you delete
the memory once you're done (and if you're deleting the derived object via a base pointer, make to god sure that the base destructor is virtual to avoid memory leaks - as we talked about earlier)!