Hello again! In this video, we are going to look at interfaces and virtual functions. When we write a
class hierarchy, we are usually modelling some kind of system, which contains various entities.
We make the basic class some kind of generalized or abstracted version of these entities, which has
all the things that these entities have in common.
So for example, if we have graphical objects, we could have a shape class as the base. Or we could
have a class like vehicle or animal.
And then the derived classes in the hierarchy will represent concrete entities within the problem domain.
So circle or aeroplane or cow, for example.
The base class represents the interface to the hierarchy; the public virtual member functions will
present all the functionality which the classes in the hierarchy have in common.
If we have a hierarchy of graphical objects, then any kind of shape object can be drawn on the screen,
rotated, moved around the screen, and so on.
In the derived classes, we are going to override these virtual functions.
We are going to implement them in a way which will be specific to that particular, concrete class.
So for example, our circle class can draw a circle shape, rotate a circle, move a circle across
the screen and so on.
When we write our base class and we have to provide these public virtual member functions, we have a
bit of a problem.
Usually, these virtual functions cannot actually do anything useful.
How would you draw a generic shape?
What kind of food does a generic animal eat?
How fast can a generic vehicle go?
So one possibility is just to define these member functions with empty bodies.
C++ does in fact provide a special syntax for this situation, which will say that these virtual functions
are not implemented here, but they will be implemented in a child class. And that helps us express
the idea of the base class as the interface to the hierarchy.
The syntax for doing this is a bit strange.
Instead of putting a pair of braces, you put " = 0".
So, "virtual void draw() = 0".
That means that draw is a so-called pure virtual function, which means it is not implemented in this
class.
It also means that the base class Shape is an abstract base class, so we cannot create any objects.
So this makes it clear that the virtual function draw() is part of the interface, and not part of the implementation
of a Shape object.
So when we have a class with a pure virtual member function, we cannot instantiate it. So if we try to
do that, we get a compiler error.
This is known as an abstract base class, which is similar to "interfaces" in other languages.
If we inherit from an abstract base class, then our child class must override all the pure virtual member
functions that it inherits from the base class. If it does not do that, then it will have pure virtual
member functions, which means that our class is itself going to be an abstract base class.
So let's try this out, we have our familiar hierarchy.
This time, the draw() member function in the base is going to be a pure virtual member
function. Equals zero.
And then, in the main function, we are just going to create an object of this class. So, er,
well, I was going to ask you what happens, but if you can read, you already know!
So there we are. We get a compiler error.
Cannot instantiate abstract class.
So when you have an abstract base cast, you cannot create any objects of that class.
So let's comment this out for a minute, because it does not work.
When we have derived classes, these are going to inherit the pure virtual functions, and they will be
still pure.
So if we have a derived class, which does not implement the pure virtual function, which does not override it,
then that is going to be an abstract base class.
So if we try to create an object of the Triangle class,
what do you think will happen?
We get the same compiler error again. Cannot instantiate abstract class.
So this might all seem a bit elaborate, but there is actually one situation where it does help you.
In C++,
if you do not say that you are passing an argument by reference or by address, then it's passed by
value, which means that the copy constructor is called.
So let's say
we have some code like this, where we meant to use dynamic binding, but we forgot to put in the reference
or the pointer symbol.
If we pass a Circle to this, then this Circle is going to be copied, the copy constructor called
will be for the Shape object.
So inside this function, we have a local variable s, which is a copy of the Circle object, but it only
has the Shape part of the object.
So it is as if the Circle part of the object had been sliced off.
And this is known as "object slicing".
So let's look at an example of this.
We've gone back to using a normal, virtual draw() function in the base class.
So this is not pure virtual.
This is an ordinary base class and not an abstract base class.
Then we have our draw_shape(), where we forgot to put the reference symbol in.
Then we have a main function which creates a Circle object and passes it to this draw_shape() function.
So what do you think is going to happen?
So that calls the draw member function of the Shape class.
And you may say, "Ah, yes, but that's because you are not using dynamic binding here. You should be using a
reference or a pointer".
OK, so let's try using a pointer.
(Yep, great C++ syntax!)
And there we are.
We still get the draw() member function of Shape, so that means that the dynamic type of the variable s
is is actually a Shape class.
And that is because the copy constructor for Shape was called.
So if we now make this a pure virtual function...
(Must remember to make this const!)
And now we get a compiler error, and can you guess what the compiler error is? "Cannot instantiate
abstract class".
OK, so that tells us that we are doing something wrong.
So, oh yes, we need to use a reference here, don't we?
And then it compiles. And then it does use dynamic binding.
So in this case, we are passing Shape by reference. We get the dynamic type and we are calling through a reference.
Okay, so just to quickly sum that up. An abstract base class cannot be passed by value.
If you have code like this and Shape is an abstract base class, then this does not compile.
So you are forced to pass it by reference or by address.
And then when you do that, s will have the dynamic type.
of the original argument, so Circle or whatever. And then when you call it through that reference or through
the pointer, then you will get the member function of the original object, using dynamic binding.
OK, so that is it for this feature.
I will see you next time.
Until then, keep coding!