WEBVTT

00:00.180 --> 00:03.680
Hello again! In this video, we are going to look at virtual destructors.

00:04.890 --> 00:08.940
I am going to start off by asking you a quiz, or posing a challenge.

00:09.990 --> 00:12.030
We have seen this quite a few times before.

00:12.390 --> 00:17.390
We have a pointer to a base class, which is actually the address of a child object.

00:17.700 --> 00:20.310
And this child object is allocated on the heap.

00:20.850 --> 00:24.090
So we need to call delete to release the memory.

00:24.780 --> 00:26.970
And the question is: which destructors get called?

00:27.660 --> 00:29.030
So this may not be straightforward.

00:29.040 --> 00:32.160
If you remember, the object in memory looks like this.

00:32.670 --> 00:39.150
So we have the Shape part of the object, the base class. Followed by the child part of the object, the

00:39.330 --> 00:43.470
extra bits for the Circle, and the pointer will be to the start of this object.

00:44.940 --> 00:47.400
So if we call delete on this pointer, what will happen?

00:50.830 --> 00:56.500
So we can get back to this code we have used before. We are going to allocate a child object. Then we are going

00:56.500 --> 01:00.610
to use a pointer to it, and then we are going to delete through that pointer.

01:01.180 --> 01:03.790
So this should allow us to find out what happens.

01:05.110 --> 01:11.320
I've also added some destructors to the Shape and the child class, which print out a message.

01:11.770 --> 01:13.720
So this will tell us which destructors get called.

01:16.220 --> 01:20.450
So there we are. We have dynamic binding, which calls the Circle's version of draw().

01:20.900 --> 01:25.760
We have good bye from Shape, so the Shape destructor gets called, but nothing from Circle.

01:26.540 --> 01:27.680
So why is that?

01:30.610 --> 01:36.400
The answer is that in this context, anyway, destructors behave just like every other kinds of member function.

01:36.910 --> 01:39.040
If they are virtual, we get dynamic binding.

01:39.400 --> 01:42.160
If they are not virtual, we just get static binding.

01:42.880 --> 01:49.890
So in the static binding case, the compiler sees pointer to Shape, so it puts in a call to the Shape

01:49.900 --> 01:50.800
destructor only.

01:52.430 --> 01:58.340
In the dynamic binding case, the run time knows that it is dealing with a Circle object, so it calls

01:58.340 --> 02:01.850
the Circle destructor, and then the Shape destructor gets called.

02:02.360 --> 02:03.890
So that all works as it should do.

02:07.850 --> 02:13.850
The reason why I'm making a video about this is because of the way that the compiler synthesizes destructors.

02:14.840 --> 02:21.230
You remember, if we have a class and we do not implement a destructor for the class, then the compiler

02:21.230 --> 02:23.600
will synthesise one for us, which does nothing.

02:24.770 --> 02:28.340
And the point here is that the default constructor is not virtual.

02:29.720 --> 02:33.530
That was a decision that was taken a very long time ago in C++.

02:34.340 --> 02:38.810
The reason is that having a virtual member function has a small amount of overhead.

02:39.380 --> 02:43.670
And if your class is not supposed to be a base class, then that overhead is wasted.

02:44.870 --> 02:51.800
One of the design principles of C++ is there should be no avoidable overhead. Or, as Bjarne Stroustrup puts

02:51.860 --> 02:52.060
it,

02:52.610 --> 02:55.040
"You should not have to pay for anything that you do not eat".

02:56.430 --> 03:03.660
So the extra overhead of a virtual function is not required, unless you have a base class. And if you

03:03.660 --> 03:07.200
have a base class, then you can put it there yourself, which is all good.

03:07.200 --> 03:10.770
But this can give us a problem if we're deleting through pointer to base.

03:11.790 --> 03:15.870
If the child part of the object is not released, then we have a memory leak.

03:16.500 --> 03:22.350
If the child's class manages some resource and the destructor is supposed to release that resource, and

03:22.350 --> 03:25.320
the destructor is not called, then we have a resource leak.

03:26.940 --> 03:33.120
And we may also get other forms of undefined behaviour, if the program relies on the child's destructor

03:33.120 --> 03:35.730
doing something, and the destructor is not called.

03:36.120 --> 03:38.910
Then the program is going to be in an inconsistent state.

03:41.790 --> 03:43.560
So fixing this is very straightforward.

03:43.620 --> 03:50.610
We just need to implement a destructor, which is virtual, and does nothing. So we can have a virtual

03:50.610 --> 03:56.400
destructor with an empty body in any version of C++. If we want to get a bit more modern, we can

03:56.400 --> 04:03.000
make this "equals default", from C++ 11 onwards, and the compiler will generate an empty body for us.

04:04.610 --> 04:09.560
There is no need to influence any other special member functions, like the constructor or the assignment

04:09.560 --> 04:12.080
operator, unless you were going to do that anyway.

04:13.040 --> 04:19.490
So as a general rule, if a class has virtual functions, it should have a virtual destructor. There are one

04:19.490 --> 04:24.320
or two unusual cases where you can get away without having any virtual destructor, but you can deal

04:24.320 --> 04:28.280
with those when you get to them, if they are going to cause efficiency problems.

04:29.970 --> 04:35.630
So let's go back to this code. We make the destructor virtual in the base class.

04:36.300 --> 04:37.610
And now, what do you think will happen?

04:41.610 --> 04:47.550
So we get "goodbye from Shape", but first we get "goodbye from Circle". So the Circle destructor

04:47.550 --> 04:50.260
is called, followed by the Shape destructor.

04:50.970 --> 04:56.760
So as a rule, if you have any virtual member functions in your class, then you need to have a virtual

04:56.970 --> 04:57.410
destructor.

04:58.170 --> 05:02.730
And one other rule that I mentioned before is using the "override" key word,

05:02.730 --> 05:05.760
every time you have a virtual function and you override it.

05:07.150 --> 05:09.280
I actually forgot my own advice when I was doing this.

05:10.630 --> 05:14.110
Which was a bit of a problem, because I also forgot to make this member function const.

05:14.590 --> 05:16.330
So what effect do you think this will have?

05:20.590 --> 05:26.440
If a member function is const, then it actually has a different signature from the same member function which

05:26.440 --> 05:26.860
is not const.

05:27.430 --> 05:29.800
So you can have two versions which are const and not const.

05:30.520 --> 05:35.410
So this is actually an overload of the draw() member function, not an override.

05:35.920 --> 05:37.840
So the base class version always gets called.

05:39.790 --> 05:42.040
And there we go. "Drawing a generic Shape".

05:46.040 --> 05:50.510
And if I had used the override key, if I had followed my own advice, then this would have been

05:50.510 --> 05:56.720
obvious straightaway, because the component spots it. "Did not override any base class methods".

05:58.450 --> 06:02.170
So when you have virtual functions in your class, always have a virtual destructor.

06:02.530 --> 06:06.820
And when you override a virtual function, always declare it as an override.

06:10.940 --> 06:12.620
Okay, so that is it for this video.

06:13.010 --> 06:15.890
I will see you next time, but until then, keep coding!
