Hello again! In this video, we are going to look at virtual destructors.
I am going to start off by asking you a quiz, or posing a challenge.
We have seen this quite a few times before.
We have a pointer to a base class, which is actually the address of a child object.
And this child object is allocated on the heap.
So we need to call delete to release the memory.
And the question is: which destructors get called?
So this may not be straightforward.
If you remember, the object in memory looks like this.
So we have the Shape part of the object, the base class. Followed by the child part of the object, the
extra bits for the Circle, and the pointer will be to the start of this object.
So if we call delete on this pointer, what will happen?
So we can get back to this code we have used before. We are going to allocate a child object. Then we are going
to use a pointer to it, and then we are going to delete through that pointer.
So this should allow us to find out what happens.
I've also added some destructors to the Shape and the child class, which print out a message.
So this will tell us which destructors get called.
So there we are. We have dynamic binding, which calls the Circle's version of draw().
We have good bye from Shape, so the Shape destructor gets called, but nothing from Circle.
So why is that?
The answer is that in this context, anyway, destructors behave just like every other kinds of member function.
If they are virtual, we get dynamic binding.
If they are not virtual, we just get static binding.
So in the static binding case, the compiler sees pointer to Shape, so it puts in a call to the Shape
destructor only.
In the dynamic binding case, the run time knows that it is dealing with a Circle object, so it calls
the Circle destructor, and then the Shape destructor gets called.
So that all works as it should do.
The reason why I'm making a video about this is because of the way that the compiler synthesizes destructors.
You remember, if we have a class and we do not implement a destructor for the class, then the compiler
will synthesise one for us, which does nothing.
And the point here is that the default constructor is not virtual.
That was a decision that was taken a very long time ago in C++.
The reason is that having a virtual member function has a small amount of overhead.
And if your class is not supposed to be a base class, then that overhead is wasted.
One of the design principles of C++ is there should be no avoidable overhead. Or, as Bjarne Stroustrup puts
it,
"You should not have to pay for anything that you do not eat".
So the extra overhead of a virtual function is not required, unless you have a base class. And if you
have a base class, then you can put it there yourself, which is all good.
But this can give us a problem if we're deleting through pointer to base.
If the child part of the object is not released, then we have a memory leak.
If the child's class manages some resource and the destructor is supposed to release that resource, and
the destructor is not called, then we have a resource leak.
And we may also get other forms of undefined behaviour, if the program relies on the child's destructor
doing something, and the destructor is not called.
Then the program is going to be in an inconsistent state.
So fixing this is very straightforward.
We just need to implement a destructor, which is virtual, and does nothing. So we can have a virtual
destructor with an empty body in any version of C++. If we want to get a bit more modern, we can
make this "equals default", from C++ 11 onwards, and the compiler will generate an empty body for us.
There is no need to influence any other special member functions, like the constructor or the assignment
operator, unless you were going to do that anyway.
So as a general rule, if a class has virtual functions, it should have a virtual destructor. There are one
or two unusual cases where you can get away without having any virtual destructor, but you can deal
with those when you get to them, if they are going to cause efficiency problems.
So let's go back to this code. We make the destructor virtual in the base class.
And now, what do you think will happen?
So we get "goodbye from Shape", but first we get "goodbye from Circle". So the Circle destructor
is called, followed by the Shape destructor.
So as a rule, if you have any virtual member functions in your class, then you need to have a virtual
destructor.
And one other rule that I mentioned before is using the "override" key word,
every time you have a virtual function and you override it.
I actually forgot my own advice when I was doing this.
Which was a bit of a problem, because I also forgot to make this member function const.
So what effect do you think this will have?
If a member function is const, then it actually has a different signature from the same member function which
is not const.
So you can have two versions which are const and not const.
So this is actually an overload of the draw() member function, not an override.
So the base class version always gets called.
And there we go. "Drawing a generic Shape".
And if I had used the override key, if I had followed my own advice, then this would have been
obvious straightaway, because the component spots it. "Did not override any base class methods".
So when you have virtual functions in your class, always have a virtual destructor.
And when you override a virtual function, always declare it as an override.
Okay, so that is it for this video.
I will see you next time, but until then, keep coding!