Hello again!
In this video, we are going to look at unique_ptrs and polymorphism.
Polymorphism is when we have a class hierarchy, and we can use an object of the base class to represent
any developed class object in that hierarchy.
In C++, we have to have a pointer to the base or a reference to the base, to be able to do that.
We could have some code like this.
We have some Shape objects which will make up a diagram or a picture, and we want to use a vector to
build up the elements of this picture.
And then, once we have got it,
we can iterate through the elements and draw the picture.
So we have a vector of Shapes, and then we add Circles, Triangles and Squares to this vector.
And then we iterate through the vector, and we call the draw member of each [object].
For this to work with polymorphism, we need to use a pointer to the base class. And this more-or-less
forces us to allocate the child objects, using new.
So in here, we are going to get a pointer to a Circle object.
We are going to store this in the vector, as a pointer to a Shape object.
And then, when we iterate, we call the virtual draw() member function, and that will call the draw()
member function of the Circle. And the same for the Triangle and the Square. Because we are using new, we have
to manage the memory ourselves.
So we have to remember to release the memory,
once we have finished with this vector.
So let's try this out.
We have our Circle, Triangle and Square.
We have a virtual draw() member function function.
We have our vector of pointers to Shapes, and then we allocate Circle, Triangle and Square objects, and
we push them onto the vector.
Then we iterate and we call the virtual member function. And then we need to remember to delete
all the pointers in this vector, once we have finished using the vector.
So there we are.
Now that we know about unique_ptrs, we should use them wherever we can.
If you see a call to new and a traditional pointer, then you should think about whether that could
be replaced by using a unique_ptr.
And in this case, it can.
It is a little bit involved.
But on, the other hand, we do not have to manage the memory anymore.
So we call make_unique().
We use the derived type as the parameter.
So this will allocates the memory for the child class. And it will return a unique_ptr to the child
class.
And then we move that into a unique_ptr to the base class.
So this "pBase" is a unique_ptr to a base class object, which is actually pointing to a derived class
object.
So this gives us all the advantages of having a unique_ptr, and it avoids all the drawbacks of using a
traditional pointer.
So the codes would look like this, with the same classes
again. We have our vector, which is now a vector of unique_ptrs to Shape.
Then instead of calling new, we call make_unique(), to create the child class objects.
And then we iterate and call the draw() member function. And we do not need to remember to
call delete.
When the function returns, the destructor for the vector will be called. This
will call the destructors for the elements. With the pointer destructor, that does nothing, but with the
unique_ptr destructor, that will release the memory.
So we do not need to worry about memory management in this code.
And yes, it does give the right answer!
You may have heard about "design patterns".
There was a very famous book, published in the 1990s, which set out several design patterns.
So these are approaches for doing object oriented programming.
And one of the best known is the factory pattern. In the factory pattern,
you have a function which creates new objects.
And the type of the object will depend on the arguments to the function.
So, in C++, you can use this with a class hierarchy.
The arguments to the function will say what kind of object to create, which child object.
And then you return it through a pointer to the base class.
So this means that all the code for creating objects in the hierarchy is in a single place, in the factory
function.
It is very flexible.
You just pass an argument, which will decide which child object gets created, and you get a polymorphic
object back.
And if you add new child classes, it is very easy to extend the code.
You just go to this function and make the necessary changes.
The traditional way to write a factory pattern in C++ is to use new to create the object, then [return]
a pointer, which has to be deleted by the caller.
So it looks like this.
So here is our factory function.
create_shape().
This will return a pointer to the base class.
The argument here, is the number of sides that the object has.
So if we have one side, that is a circle.
If it has three sides, it is a triangle.
And if it has four sides, it is a square.
So we call new to create the object, and then we return
that, through a pointer to the base class.
And then, in our main() function we call create_shape(), we pass the number [as the] argument.
So 3 is going to create a triangle object.
So "pShape" will be a pointer to the base class, which is pointing to a Triangle object.
And then we can call its draw() function, and then we need to release the memory.
And there we are.
We have drawn a triangle.
So again, can we implement this using a unique_ptr?
Yes, we can.
So we create a unique_ptr to the child class, as a local variable.
Then we return
that unique_ptr from the function.
This is going to be moved into a unique_ptr object in the caller.
So the allocated memory has been transferred, from this local variable, into the caller's unique_ptr.
And then, when that object goes out of scope in the caller, the destructor will be called and the memory
will automatically be released.
So now, our factory function returns a unique_ptr to the base class. We call make_unique() to create
the child class object.
And when we call the function, we move the return value into our own unique_ptr.
And then we can just call the virtual member function on the pointer.
And when the object goes out of scope, the memory will be released automatically.
So there we are.
Okay, so that is it for this video.
I will see you next time.
Until then, keep coding!