WEBVTT

00:00.150 --> 00:00.690
Hello again!

00:01.020 --> 00:04.710
In this video, we are going to look at unique_ptrs and custom deleters.

00:06.630 --> 00:12.210
We know there are two ways to create a unique_ptr object. we can call that make_unique(), which

00:12.210 --> 00:17.850
calls new internally, and then move the return value from that into the unique_ptr object.

00:18.360 --> 00:21.660
And that's the recommended way to do it, because there's less scope for things to go wrong.

00:23.160 --> 00:26.940
We can also call the constructor directly and pass a traditional pointer.

00:27.420 --> 00:33.360
This should be something that has been allocated with new. And then the unique_ptr object will, if

00:33.360 --> 00:34.920
you like, adopt this pointer.

00:35.310 --> 00:38.190
It will be responsible for managing its lifetime.

00:41.540 --> 00:46.820
But, looking at this, unique_ptr is an object which implements RAII.

00:47.270 --> 00:53.690
It could, in theory, accept any traditional pointer. So we could use unique_ptr as a generic class

00:53.690 --> 00:54.950
for managing resources.

00:55.220 --> 00:59.750
We just pass a pointer to the resource, in the unique_ptr constructor.

01:01.250 --> 01:06.650
Unfortunately, there is a bit of a problem, because the destructor for unique_ptr calls delete.

01:07.190 --> 01:11.870
And if you call delete, and the pointer was not returned by new, then you have a big problem.

01:15.030 --> 01:16.470
So let's look at an example.

01:16.890 --> 01:19.920
Let's imagine we have a C networking API.

01:20.670 --> 01:26.820
It has this data structure, with details of the computer we are connecting to. And it has another data

01:26.820 --> 01:30.360
structure which has details about the connection status, and so on.

01:32.070 --> 01:37.950
It has a function which will open a connection to a destination and return one of these connection objects,

01:38.820 --> 01:41.580
and then a function which will close a given connection.

01:42.120 --> 01:46.380
And obviously, this is a mock up, and it is not meant to be a real networking API!

01:50.100 --> 01:57.210
Then we want to write a function, a C++ function, which will take a destination, and then create a

01:57.210 --> 01:59.550
connection object and manage the connection.

02:00.240 --> 02:03.300
So this is going to use a unique_ptr to a connection.

02:03.570 --> 02:08.390
So this will have a pointer member which is pointing to a connection object. And then we give it

02:08.410 --> 02:11.520
the address of this connection as the argument to the constructor.

02:13.780 --> 02:16.120
The problem comes when we return from this function.

02:16.630 --> 02:20.470
The destructor of unique_ptr will be called, and that will call delete.

02:21.010 --> 02:22.600
And we have a pointer which is on the stack.

02:23.140 --> 02:24.880
So that is probably going to crash horribly.

02:26.290 --> 02:27.280
And yes, there it is.

02:28.510 --> 02:30.580
Yes, the problem is "invalid heap pointer".

02:31.090 --> 02:33.820
And we cannot really argue with that. It is invalid!

02:34.520 --> 02:35.830
Fortunately, unique_ptr

02:35.830 --> 02:37.000
does provide for this.

02:37.420 --> 02:39.880
We can provide a so called "deleter".

02:40.450 --> 02:45.130
So this will be a callable object, and the destructor will call our deleter.

02:45.640 --> 02:48.490
Instead of calling the library delete function.

02:49.270 --> 02:54.070
When this happens, the managed pointer, the member of the unique_ptr object, will be passed to

02:54.070 --> 02:59.020
our deleter function. And then we can do whatever we need, to release the resource.

02:59.530 --> 03:03.550
In this case, we call disconnect(), to close the network connection.

03:07.090 --> 03:09.010
So our deleter could look like this.

03:09.010 --> 03:10.720
We could write a lambda expression.

03:11.320 --> 03:13.540
This is going to take a pointer member as the argument.

03:13.630 --> 03:15.460
So it is going to take pointer to connection.

03:16.090 --> 03:20.290
And then we are going to call disconnect() with that as the argument.

03:21.810 --> 03:24.240
And this will be called when the unique_ptr is destroyed.

03:24.570 --> 03:27.480
So that will make sure that the network connection is always closed,

03:27.810 --> 03:29.340
even if an exception gets thrown.

03:32.000 --> 03:35.930
So how do we get this deleter into the unique_ptr? The deleter

03:35.940 --> 03:38.090
is actually part of the type of the unique_ptr.

03:38.090 --> 03:43.220
We need to give its type as a second, optional, template parameter.

03:44.660 --> 03:49.280
And there is a problem there, because we need to know the type, to give it as a template parameter.

03:49.790 --> 03:51.380
But this is a lambda expression.

03:51.770 --> 03:56.840
It is a class that was created by the compiler. So we do not know what its name is, but the compiler

03:56.840 --> 03:57.170
does.

04:00.200 --> 04:03.620
Fortunately, C++ has something which can help us here.

04:04.220 --> 04:09.020
We are not actually going to cover it in detail until later, but there is something called decltype.

04:09.470 --> 04:13.250
And this will provide the type of the expression that we give to it.

04:14.510 --> 04:21.020
So if we put decltype(end_connection), the compiler will replace this by the type of end_connection().

04:21.290 --> 04:26.210
So this will be the name of the class, that the compiler generated, to create the lambda function.

04:27.110 --> 04:30.140
And then we need to add that to the template parameters.

04:30.140 --> 04:35.480
So our unique_ptr will have a member which is pointer to connection, and it has a deleter whose

04:35.480 --> 04:38.180
type is the same type as end_connection().

04:39.230 --> 04:42.170
And then, finally, we need to pass the argument to the constructor.

04:42.650 --> 04:48.500
So we pass the pointer to the connection, as the first argument, and the lambda expression as the second

04:48.500 --> 04:49.010
argument.

04:51.460 --> 04:58.030
So our code would now look like this. We have our lambda expression to close the connection.

04:58.060 --> 04:59.380
That is going to be our deleter.

05:00.520 --> 05:06.250
Then we say that we have a unique_ptr to a connection object, whose deleter is the same type as this.

05:07.210 --> 05:10.270
And then we initialize the member and the deleter.

05:13.780 --> 05:14.080
Okay.

05:14.080 --> 05:15.550
So that runs without crashing, so

05:15.760 --> 05:20.890
let's release it. And let's quickly see what happens if we do throw an exception.

05:28.620 --> 05:30.690
So we are going to adopt the pointer, here.

05:30.960 --> 05:33.360
Then we throw the exception, before we get any data.

05:34.080 --> 05:37.200
So let's see if the destructor gets called and the connection gets closed.

05:38.430 --> 05:39.210
And yes, it does.

05:39.660 --> 05:43.320
So the connection is closed and then the exception gets caught.

05:44.130 --> 05:45.570
Okay, so that is it for this video.

05:45.990 --> 05:46.830
I will see you next time.

05:47.070 --> 05:49.010
Until then, keep coding!
