Hello again! In this video, we are going to look at polymorphism. Polymorphism comes from
Greek for "many forms". In programming, polymorphism means that different types have the same interface,
which means they have the same behaviour.
One example that we have seen are the containers in the standard template library. For example, a vector
of int is technically a different type from a vector of string.
But they both have the same interface.
We can call the same public functions on a vector of int as we can on a vector of string.
The only thing that differs is the element type.
This is known as parametric polymorphism, because the element's type is a parameter of the vector.
When we have classes in an inheritance hierarchy, this is also polymorphism because all the classes
have the same interface, which is the set of public member functions of the base class.
So for example, we have our Shape, Circle and Triangle, which all have the draw() member function.
This is known as subtype polymorphism, because Circle and Triangle are subtypes of the Shape type.
In more formal terms, an object of a type can be replaced by an object of its subtype.
So anywhere that uses a base class can also use a derived class.
So with our draw_shape() function, which uses a Shape class, we could also provide a Circle or a Triangle
object as the argument to that call.
This is known as the Liskov substitution principle.
And it is one of the fundamental concepts in object-oriented programming.
Polymorphism is a useful programming technique. If we have types which are related, we can just write
code once and it will handle all of them.
So for example, with our draw_shape() function, we just have one function which can handle any object
in the Shape hierarchy.
So that avoids having to write a separate function for each class.
We can add new types without having to write any extra code to handle them.
So when we add the Triangle class, we do not need to modify the draw_shape() function or change the container
of Shapes code.
It just works. And that saves us time.
But we still get the correct behaviour for each individual type, so when we pass a Triangle to draw_shape(), it
draws a Triangle and not some other object.
In C++, we can do sub-type polymorphism by getting a pointer to a base class, or a reference to
a base class, and calling virtual member functions through the reference or pointer. As we have done
with our container of Shapes, quite a few times.
So this means we can use the same code to handle different classes, but it is still all type-safe,
which is an important feature of C++.
So when the program runs, it will work out the correct way to draw each of these Shape objects. Or, if
you like, a different way to think about it: each of these Shapes in the container knows how to draw itself.
Let's compare these two types of polymorphism. With sub-type polymorphism, as it is implemented in C++,
we have run-time overhead.
The program has to decide which virtual function is called, at run-time.
It may require we management, if we are going to use new to get a pointer to a base class object.
We have no control over what classes people are going to derive from our base class, or our hierarchy.
People might come up with all sorts of weird and wonderful things, and we cannot stop them.
And also, you can end up getting large hierarchies, which are difficult to understand and to work with.
With parametric polymorphism, all the decisions are made at compile time, there is no one time overhead.
There is no need for any memory allocation.
And you can also decide which types you're willing to accept as a parameter.
There's something called "SFINAE", which we will look at briefly when we do compile time programming.
It is pretty complicated!
In C++ 20, we now have concepts which are an easier way to do that, but it is still not fully supported.
So there are quite a lot of advantages to parametric polymorphism, but the coding is more complicated.
At least until we get concepts fully implemented.
So the question is, when should we use inheritance? And usually the answer is no, we should not use
inheritance.
It tends to be overused. Quite often when people are trying to fix a bug or add a feature and they have
a tight deadline.
They tend to grab a class that does something vaguely similar, inherit from it and then use their
derived class to fix all the things that they do not like.
That is not really the correct approach.
If you just want to call the class's interface, then you should make an object of that class a data
member, and then call the public member functions.
So that is composition. And quite often, composition is a better solution than inheritance, because it
reduces coupling.
In C++, we saw there are advantages to parametric polymorphism, and the trend is away from sub-types towards
parametric.
In fact, the C++ Library is absolutely full of parametric polymorphism, but
I think there is only two or three places where it uses sub-type polymorphism.
So in conclusion, my recommendation: only use inheritance if you need a family relationship, a parent-
child relationship. If you have an "is-a" or "is-kind-of" relationship between your proposed parent and your
proposed child class, than it can be appropriate to use inheritance.
Otherwise, you can probably find a better solution.
Okay, so that is it for this feature.
I will see you next time, but meanwhile, keep coding!