WEBVTT

00:00:00.540 --> 00:00:03.230
Let's talk more about pointers to objects.

00:00:03.230 --> 00:00:05.310
To follow along with this lesson,

00:00:05.310 --> 00:00:09.550
you should already be familiar with class polymorphism through inheritance,

00:00:09.550 --> 00:00:13.060
which can be used to build a hierarchy of derived classes.

00:00:13.440 --> 00:00:17.550
I will quickly go through the classes I have prepared for this demonstration.

00:00:17.940 --> 00:00:20.350
The Instrument class is the base class,

00:00:20.350 --> 00:00:25.740
and it only defines if the instrument is electric or acoustic.

00:00:25.740 --> 00:00:29.690
Default constructor will assume that the instrument is acoustic,

00:00:29.690 --> 00:00:32.960
but you can change that by using the second constructor.

00:00:33.340 --> 00:00:35.810
The class also has a play member function,

00:00:35.810 --> 00:00:38.160
which just prints out a simple message.

00:00:39.140 --> 00:00:41.660
Okay, let's move on to derived classes.

00:00:42.040 --> 00:00:45.070
The first derived class is the Guitar class,

00:00:45.080 --> 00:00:48.140
which only defines one additional property,

00:00:48.150 --> 00:00:52.500
the number of strings, and this is strings as in guitar strings,

00:00:52.500 --> 00:00:54.260
not the strings of characters.

00:00:54.640 --> 00:00:57.890
The default constructor sets the number of strings to six,

00:00:57.890 --> 00:01:00.820
but we also have an option to call one of the other

00:01:00.820 --> 00:01:03.360
constructors to manually set that number.

00:01:04.040 --> 00:01:06.890
You can even pass the Boolean value to the Instrument

00:01:06.890 --> 00:01:09.650
constructor to make this guitar electric.

00:01:10.040 --> 00:01:10.870
And of course,

00:01:10.870 --> 00:01:14.190
there is a special version of the play function for the guitar class,

00:01:14.190 --> 00:01:18.760
which will override the play function from the bass Instrument class.

00:01:19.740 --> 00:01:23.150
Finally, there is also another derived Synth class,

00:01:23.150 --> 00:01:27.940
which has a smaller definition, because we assume that every synthesizer is

00:01:27.950 --> 00:01:32.370
electric, so we only have two constructors. But of course,

00:01:32.370 --> 00:01:34.920
Synth also has a function with the special message,

00:01:34.920 --> 00:01:37.250
which overrides the base play function.

00:01:37.740 --> 00:01:40.130
Okay, now that we are familiar with the classes,

00:01:40.140 --> 00:01:41.960
I will show you the main function.

00:01:42.340 --> 00:01:45.750
I instantiated one instrument, one electric guitar,

00:01:45.760 --> 00:01:50.260
one seven‑string guitar, and one synth with 25 keys.

00:01:50.740 --> 00:01:54.850
I will make each one of them call their own version of the play function.

00:01:56.440 --> 00:01:59.950
If I compile this, everything should work as expected.

00:02:00.440 --> 00:02:03.350
All of these messages are printed out correctly for

00:02:03.350 --> 00:02:05.150
that specific type of object.

00:02:06.040 --> 00:02:09.789
Now let's ruin this harmony by introducing pointers.

00:02:10.080 --> 00:02:11.960
Since we will work with pointers,

00:02:11.960 --> 00:02:15.360
let's make this example a little bit more realistic by actually

00:02:15.360 --> 00:02:18.550
using these pointers for heap memory allocations.

00:02:18.940 --> 00:02:21.940
I am still allocating the same objects, but this time

00:02:21.950 --> 00:02:23.760
they will be stored on the heap.

00:02:24.140 --> 00:02:27.350
All of these variables are now pointers, so we need to

00:02:27.350 --> 00:02:30.050
de‑reference them before calling the play function.

00:02:30.440 --> 00:02:31.650
In the previous lesson,

00:02:31.650 --> 00:02:34.850
we learned that this is easier to do with an arrow operator.

00:02:35.240 --> 00:02:36.220
And finally,

00:02:36.220 --> 00:02:40.960
let's also call delete on each one of these pointers to prevent memory leaks.

00:02:42.040 --> 00:02:45.490
This version of the program will still work as expected,

00:02:45.490 --> 00:02:49.370
but what if I want to point to all of these objects with

00:02:49.370 --> 00:02:51.160
the pointer to an instrument?

00:02:51.540 --> 00:02:53.460
The purpose of this will be shown later.

00:02:53.460 --> 00:02:56.060
For now, let's just try it as an experiment.

00:02:57.140 --> 00:02:58.760
Let's try to compile this.

00:02:59.640 --> 00:03:03.390
And as you can see, only the play function from the Instrument

00:03:03.390 --> 00:03:07.160
class is called; the overrides are being ignored.

00:03:08.040 --> 00:03:12.560
This is because of something called early binding or compile‑time binding.

00:03:12.940 --> 00:03:16.630
The compiler will see that this is a pointer to an instrument, and

00:03:16.630 --> 00:03:20.290
it will assume that the object that this pointer is pointing to is

00:03:20.300 --> 00:03:23.260
instantiated from the Instrument class.

00:03:23.640 --> 00:03:27.910
Since every derived class also contains all of the public parts of the

00:03:27.910 --> 00:03:32.070
base class, compiler will make this pointer call the play function from

00:03:32.070 --> 00:03:36.850
the Instrument class, because we are making a call from the instrument

00:03:36.850 --> 00:03:42.660
pointer. To solve this problem, C++ offers us the concept of virtual functions.

00:03:43.040 --> 00:03:44.870
When you make a function virtual,

00:03:44.880 --> 00:03:47.720
you are informing the compiler that this specific

00:03:47.720 --> 00:03:50.660
function should be bound at runtime,

00:03:50.670 --> 00:03:55.600
which will let you implement the dynamic runtime polymorphism. To make the

00:03:55.600 --> 00:03:59.800
function virtual, we need to add the virtual keyword before the function

00:03:59.800 --> 00:04:05.150
definition inside of the base class. And this is all we need to do. All of

00:04:05.150 --> 00:04:09.460
the play function definitions from the derived classes will now override this

00:04:09.460 --> 00:04:14.030
base class function, and they will be able to do that dynamically, while the

00:04:14.030 --> 00:04:15.460
program is running.

00:04:15.940 --> 00:04:19.890
A good practice is to also add an override specifier to the

00:04:19.899 --> 00:04:22.750
overridden function of the derived class.

00:04:23.140 --> 00:04:27.010
You can do that by placing the override keyword after the list of

00:04:27.010 --> 00:04:29.950
parameters inside of the functions definition.

00:04:30.340 --> 00:04:34.430
I would encourage you to do that, because if you make a mistake and don't

00:04:34.430 --> 00:04:38.660
override the virtual function correctly, the compiler will give you a warning,

00:04:38.660 --> 00:04:43.470
because it knows that each overridden function needs to match the signature of

00:04:43.470 --> 00:04:46.060
the virtual function from the base class.

00:04:47.040 --> 00:04:50.460
Let's compile again after making this function virtual.

00:04:50.840 --> 00:04:56.070
And as you can see, the instrument pointer is calling the correct function for

00:04:56.080 --> 00:05:01.550
each derived object. As opposed to early binding, virtual functions allow us to

00:05:01.550 --> 00:05:06.580
do late binding, which as the name suggests, means that we bind the correct

00:05:06.580 --> 00:05:09.650
function to the object at some later time.

00:05:10.040 --> 00:05:12.900
So even though all of these are base class pointers,

00:05:12.900 --> 00:05:15.990
the program will first check the type of the object itself

00:05:16.000 --> 00:05:18.560
before making a call to the correct function.

00:05:18.940 --> 00:05:22.790
The exact implementation of this behavior will be explained in the

00:05:22.790 --> 00:05:27.510
next lesson. And now the play function works as expected, but we

00:05:27.510 --> 00:05:31.570
still didn't address one hidden bug, and this one is really dangerous

00:05:31.570 --> 00:05:33.580
because it causes a memory leak.

00:05:34.240 --> 00:05:35.760
In one of the previous lessons,

00:05:35.770 --> 00:05:38.800
I mentioned that the size of an instantiated object is

00:05:38.810 --> 00:05:41.350
equal to the size of all of its members.

00:05:41.740 --> 00:05:46.340
Virtual functions will cause an additional overhead, but we will ignore that

00:05:46.340 --> 00:05:51.920
for now. This instrument object has only one Boolean member, so its size

00:05:51.930 --> 00:05:57.610
should be 1 byte. Guitars and synths have only one integer member, but they

00:05:57.610 --> 00:06:02.100
also inherit the Boolean from the base class, so their total size should be

00:06:02.100 --> 00:06:07.650
5 bytes, at least in theory. When we call the delete on this pointer to an

00:06:07.650 --> 00:06:11.550
instrument object, the memory will be correctly de‑allocated.

00:06:11.940 --> 00:06:12.600
However,

00:06:12.610 --> 00:06:16.310
all of these other calls to the delete operator will only

00:06:16.310 --> 00:06:19.340
delete the base part from each of these objects.

00:06:19.380 --> 00:06:22.260
They will only delete the Boolean member.

00:06:22.640 --> 00:06:25.360
This is because the compiler will still use early

00:06:25.360 --> 00:06:27.650
binding to access these objects.

00:06:28.240 --> 00:06:32.490
These dangling members from the derived classes will stay in memory

00:06:32.500 --> 00:06:36.540
and cause a serious memory leak. To fix this,

00:06:36.540 --> 00:06:39.330
we need to do the same thing we did with the play function.

00:06:39.340 --> 00:06:42.950
We need to implement late binding, and we can do that by

00:06:42.950 --> 00:06:45.550
making the base destructor virtual.

00:06:45.940 --> 00:06:48.640
I don't really have anything special to put into this

00:06:48.640 --> 00:06:51.850
destructor, so I will just make it default.

00:06:52.240 --> 00:06:56.100
We don't have to explicitly define the overridden destructor for each

00:06:56.100 --> 00:07:00.250
derived class, because it's not necessary in this case.

00:07:01.040 --> 00:07:03.110
So now that we've fixed this leak,

00:07:03.120 --> 00:07:05.860
let's talk about why we even need this feature.

00:07:06.240 --> 00:07:09.070
If you have a hierarchy of classes like this one,

00:07:09.080 --> 00:07:12.060
you can use the base class as an interface.

00:07:12.540 --> 00:07:16.870
I will first remove all of the code from the main function. Let's say that I

00:07:16.870 --> 00:07:21.260
want to form a band as an array of pointers to each instrument.

00:07:21.640 --> 00:07:25.460
This is a small band for the demonstration purposes. It only has two

00:07:25.460 --> 00:07:31.810
guitars and one synth, all of them allocated on the heap. Inheritance

00:07:31.810 --> 00:07:36.620
and late binding allow me to group derived classes in a single array

00:07:36.630 --> 00:07:41.300
of base class pointers. Since it makes no sense to have a general

00:07:41.310 --> 00:07:42.610
instrument object,

00:07:42.620 --> 00:07:46.220
I will turn this class into an abstract class by removing this

00:07:46.220 --> 00:07:49.260
play function definition and assigning it to 0.

00:07:50.340 --> 00:07:54.980
This will make this function into a pure virtual function, and every class that

00:07:54.980 --> 00:07:59.190
has a pure virtual function is known as an abstract class,

00:07:59.200 --> 00:08:04.340
which is another word for an interface in C++. Basically, abstract

00:08:04.340 --> 00:08:09.220
classes cannot be instantiated, which in our case, makes sense, since we

00:08:09.220 --> 00:08:14.480
don't need an unspecified instrument. And now, I can do something like

00:08:14.490 --> 00:08:18.610
create a for loop to loop through all of the instruments and call the

00:08:18.610 --> 00:08:21.050
play function on each one of them.

00:08:21.440 --> 00:08:22.980
In this short line of code,

00:08:22.990 --> 00:08:27.470
we could have had a big orchestra with hundreds of instruments, because

00:08:27.480 --> 00:08:30.260
we don't have to call their play function manually.

00:08:30.640 --> 00:08:33.720
And I also want to check which instrument needs electricity.

00:08:33.730 --> 00:08:38.260
So I will call this check_electricity function inside of the loop.

00:08:38.840 --> 00:08:41.059
Let me quickly define this function.

00:08:41.740 --> 00:08:44.070
The function will just take in a pointer to an

00:08:44.070 --> 00:08:46.960
instrument, and it will print a special message if the

00:08:46.960 --> 00:08:49.750
electric Boolean member is set to true.

00:08:50.340 --> 00:08:54.150
So, here is another use case for the instrument interface.

00:08:54.160 --> 00:08:55.310
If we didn't have this,

00:08:55.310 --> 00:09:00.070
we would have to overload this function for every type of instrument, or we

00:09:00.070 --> 00:09:03.260
would at least be forced to make a templated function.

00:09:03.940 --> 00:09:07.490
And finally, we cannot forget to create another loop to

00:09:07.490 --> 00:09:10.950
delete each one of these instruments from the heap.

00:09:10.950 --> 00:09:13.760
Preventing memory leaks is important.

00:09:14.640 --> 00:09:15.750
Let's compile this.

00:09:16.840 --> 00:09:18.610
If we did everything correctly,

00:09:18.620 --> 00:09:21.690
we should see the message from each play function and also

00:09:21.690 --> 00:09:24.870
the message which informs us that the electric guitar and

00:09:24.870 --> 00:09:26.760
synth need to be plugged in.

00:09:27.140 --> 00:09:30.910
So if you ever need virtual functions in your classes,

00:09:30.920 --> 00:09:34.760
always define a virtual destructor to prevent memory leaks.
