Hello again!
In this video, we are going to look at unique_ptrs and custom deleters.
We know there are two ways to create a unique_ptr object. we can call that make_unique(), which
calls new internally, and then move the return value from that into the unique_ptr object.
And that's the recommended way to do it, because there's less scope for things to go wrong.
We can also call the constructor directly and pass a traditional pointer.
This should be something that has been allocated with new. And then the unique_ptr object will, if
you like, adopt this pointer.
It will be responsible for managing its lifetime.
But, looking at this, unique_ptr is an object which implements RAII.
It could, in theory, accept any traditional pointer. So we could use unique_ptr as a generic class
for managing resources.
We just pass a pointer to the resource, in the unique_ptr constructor.
Unfortunately, there is a bit of a problem, because the destructor for unique_ptr calls delete.
And if you call delete, and the pointer was not returned by new, then you have a big problem.
So let's look at an example.
Let's imagine we have a C networking API.
It has this data structure, with details of the computer we are connecting to. And it has another data
structure which has details about the connection status, and so on.
It has a function which will open a connection to a destination and return one of these connection objects,
and then a function which will close a given connection.
And obviously, this is a mock up, and it is not meant to be a real networking API!
Then we want to write a function, a C++ function, which will take a destination, and then create a
connection object and manage the connection.
So this is going to use a unique_ptr to a connection.
So this will have a pointer member which is pointing to a connection object. And then we give it
the address of this connection as the argument to the constructor.
The problem comes when we return from this function.
The destructor of unique_ptr will be called, and that will call delete.
And we have a pointer which is on the stack.
So that is probably going to crash horribly.
And yes, there it is.
Yes, the problem is "invalid heap pointer".
And we cannot really argue with that. It is invalid!
Fortunately, unique_ptr
does provide for this.
We can provide a so called "deleter".
So this will be a callable object, and the destructor will call our deleter.
Instead of calling the library delete function.
When this happens, the managed pointer, the member of the unique_ptr object, will be passed to
our deleter function. And then we can do whatever we need, to release the resource.
In this case, we call disconnect(), to close the network connection.
So our deleter could look like this.
We could write a lambda expression.
This is going to take a pointer member as the argument.
So it is going to take pointer to connection.
And then we are going to call disconnect() with that as the argument.
And this will be called when the unique_ptr is destroyed.
So that will make sure that the network connection is always closed,
even if an exception gets thrown.
So how do we get this deleter into the unique_ptr? The deleter
is actually part of the type of the unique_ptr.
We need to give its type as a second, optional, template parameter.
And there is a problem there, because we need to know the type, to give it as a template parameter.
But this is a lambda expression.
It is a class that was created by the compiler. So we do not know what its name is, but the compiler
does.
Fortunately, C++ has something which can help us here.
We are not actually going to cover it in detail until later, but there is something called decltype.
And this will provide the type of the expression that we give to it.
So if we put decltype(end_connection), the compiler will replace this by the type of end_connection().
So this will be the name of the class, that the compiler generated, to create the lambda function.
And then we need to add that to the template parameters.
So our unique_ptr will have a member which is pointer to connection, and it has a deleter whose
type is the same type as end_connection().
And then, finally, we need to pass the argument to the constructor.
So we pass the pointer to the connection, as the first argument, and the lambda expression as the second
argument.
So our code would now look like this. We have our lambda expression to close the connection.
That is going to be our deleter.
Then we say that we have a unique_ptr to a connection object, whose deleter is the same type as this.
And then we initialize the member and the deleter.
Okay.
So that runs without crashing, so
let's release it. And let's quickly see what happens if we do throw an exception.
So we are going to adopt the pointer, here.
Then we throw the exception, before we get any data.
So let's see if the destructor gets called and the connection gets closed.
And yes, it does.
So the connection is closed and then the exception gets caught.
Okay, so that is it for this video.
I will see you next time.
Until then, keep coding!