WEBVTT

00:00.180 --> 00:06.060
Hello again! In this video, we are going to look at polymorphism. Polymorphism comes from

00:06.060 --> 00:13.230
Greek for "many forms". In programming, polymorphism means that different types have the same interface,

00:13.590 --> 00:15.390
which means they have the same behaviour.

00:18.100 --> 00:24.580
One example that we have seen are the containers in the standard template library. For example, a vector

00:24.580 --> 00:28.450
of int is technically a different type from a vector of string.

00:28.870 --> 00:30.340
But they both have the same interface.

00:30.790 --> 00:35.650
We can call the same public functions on a vector of int as we can on a vector of string.

00:36.190 --> 00:38.320
The only thing that differs is the element type.

00:39.330 --> 00:45.720
This is known as parametric polymorphism, because the element's type is a parameter of the vector.

00:50.340 --> 00:56.340
When we have classes in an inheritance hierarchy, this is also polymorphism because all the classes

00:56.340 --> 01:01.380
have the same interface, which is the set of public member functions of the base class.

01:02.160 --> 01:06.390
So for example, we have our Shape, Circle and Triangle, which all have the draw() member function.

01:07.770 --> 01:14.370
This is known as subtype polymorphism, because Circle and Triangle are subtypes of the Shape type.

01:17.640 --> 01:22.830
In more formal terms, an object of a type can be replaced by an object of its subtype.

01:23.100 --> 01:27.450
So anywhere that uses a base class can also use a derived class.

01:28.020 --> 01:34.200
So with our draw_shape() function, which uses a Shape class, we could also provide a Circle or a Triangle

01:34.200 --> 01:36.300
object as the argument to that call.

01:37.080 --> 01:39.650
This is known as the Liskov substitution principle.

01:39.990 --> 01:43.260
And it is one of the fundamental concepts in object-oriented programming.

01:45.800 --> 01:51.740
Polymorphism is a useful programming technique. If we have types which are related, we can just write

01:51.830 --> 01:54.020
code once and it will handle all of them.

01:54.380 --> 01:59.660
So for example, with our draw_shape() function, we just have one function which can handle any object

01:59.660 --> 02:00.800
in the Shape hierarchy.

02:01.340 --> 02:04.760
So that avoids having to write a separate function for each class.

02:08.180 --> 02:12.170
We can add new types without having to write any extra code to handle them.

02:12.470 --> 02:18.900
So when we add the Triangle class, we do not need to modify the draw_shape() function or change the container

02:18.980 --> 02:19.760
of Shapes code.

02:20.030 --> 02:22.130
It just works. And that saves us time.

02:23.710 --> 02:28.810
But we still get the correct behaviour for each individual type, so when we pass a Triangle to draw_shape(), it

02:28.810 --> 02:31.180
draws a Triangle and not some other object.

02:34.560 --> 02:40.740
In C++, we can do sub-type polymorphism by getting a pointer to a base class, or a reference to

02:40.740 --> 02:46.050
a base class, and calling virtual member functions through the reference or pointer. As we have done

02:46.050 --> 02:49.020
with our container of Shapes, quite a few times.

02:51.800 --> 02:56.840
So this means we can use the same code to handle different classes, but it is still all type-safe,

02:57.140 --> 02:59.240
which is an important feature of C++.

03:00.600 --> 03:06.270
So when the program runs, it will work out the correct way to draw each of these Shape objects. Or, if

03:06.270 --> 03:11.520
you like, a different way to think about it: each of these Shapes in the container knows how to draw itself.

03:16.880 --> 03:22.640
Let's compare these two types of polymorphism. With sub-type polymorphism, as it is implemented in C++,

03:23.210 --> 03:24.440
we have run-time overhead.

03:24.890 --> 03:28.280
The program has to decide which virtual function is called, at run-time.

03:29.240 --> 03:34.400
It may require we management, if we are going to use new to get a pointer to a base class object.

03:35.360 --> 03:41.690
We have no control over what classes people are going to derive from our base class, or our hierarchy.

03:42.790 --> 03:45.700
People might come up with all sorts of weird and wonderful things, and we cannot stop them.

03:46.540 --> 03:52.690
And also, you can end up getting large hierarchies, which are difficult to understand and to work with.

03:56.300 --> 04:01.910
With parametric polymorphism, all the decisions are made at compile time, there is no one time overhead.

04:02.390 --> 04:04.550
There is no need for any memory allocation.

04:05.150 --> 04:09.260
And you can also decide which types you're willing to accept as a parameter.

04:10.460 --> 04:15.010
There's something called "SFINAE", which we will look at briefly when we do compile time programming.

04:15.320 --> 04:16.460
It is pretty complicated!

04:16.820 --> 04:22.580
In C++ 20, we now have concepts which are an easier way to do that, but it is still not fully supported.

04:23.330 --> 04:29.090
So there are quite a lot of advantages to parametric polymorphism, but the coding is more complicated.

04:29.420 --> 04:32.690
At least until we get concepts fully implemented.

04:37.290 --> 04:42.300
So the question is, when should we use inheritance? And usually the answer is no, we should not use

04:42.300 --> 04:42.900
inheritance.

04:44.040 --> 04:49.410
It tends to be overused. Quite often when people are trying to fix a bug or add a feature and they have

04:49.410 --> 04:50.220
a tight deadline.

04:50.610 --> 04:55.860
They tend to grab a class that does something vaguely similar, inherit from it and then use their

04:55.860 --> 04:58.410
derived class to fix all the things that they do not like.

04:59.070 --> 05:00.450
That is not really the correct approach.

05:01.230 --> 05:06.750
If you just want to call the class's interface, then you should make an object of that class a data

05:06.750 --> 05:09.690
member, and then call the public member functions.

05:10.020 --> 05:15.540
So that is composition. And quite often, composition is a better solution than inheritance, because it

05:15.540 --> 05:16.410
reduces coupling.

05:18.170 --> 05:24.740
In C++, we saw there are advantages to parametric polymorphism, and the trend is away from sub-types towards

05:24.830 --> 05:25.490
parametric.

05:26.450 --> 05:30.380
In fact, the C++ Library is absolutely full of parametric polymorphism, but

05:30.800 --> 05:33.890
I think there is only two or three places where it uses sub-type polymorphism.

05:35.270 --> 05:41.990
So in conclusion, my recommendation: only use inheritance if you need a family relationship, a parent-

05:41.990 --> 05:48.140
child relationship. If you have an "is-a" or "is-kind-of" relationship between your proposed parent and your

05:48.140 --> 05:51.680
proposed child class, than it can be appropriate to use inheritance.

05:52.280 --> 05:54.260
Otherwise, you can probably find a better solution.

05:55.360 --> 05:56.770
Okay, so that is it for this feature.

05:57.220 --> 06:00.250
I will see you next time, but meanwhile, keep coding!
