WEBVTT

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

00:00.960 --> 00:03.180
In this video, we are going to look at unique_ptrs.

00:03.180 --> 00:08.370
unique_ptr is the best replacements in most situations.

00:08.670 --> 00:15.030
If you have a traditional pointer which manages heap memory, the unique_ptr will allocate the memory

00:15.030 --> 00:18.480
in its constructor, and it will have sole ownership of that memory.

00:19.590 --> 00:22.830
It will automatically release the memory when the object goes out of scope.

00:22.830 --> 00:28.830
So at the end of a scope, or when an exception is thrown. And this means that we can use memory which

00:28.830 --> 00:34.320
is allocated on the heap, but the actual way that we interact with the object is like any other stack

00:34.320 --> 00:34.830
object.

00:36.740 --> 00:39.490
It has the usual advantages of smart pointers.

00:39.500 --> 00:42.770
We do not need to worry about managing memory ourselves.

00:43.460 --> 00:49.070
The pointer to the allocated memory cannot be overwritten or invalidated, and there are no issues with

00:49.070 --> 00:50.000
shallow copying.

00:51.320 --> 00:53.240
unique_ptr is a very lightweight class.

00:53.540 --> 00:58.850
There is no more overhead to using a unique_ptr object than there is to using a traditional pointer.

01:02.570 --> 01:05.750
The unique_ptr class is defined in the <memory> header.

01:06.320 --> 01:10.850
It is a template class. It has a traditional pointer, which is a data member.

01:11.570 --> 01:17.570
The public member functions of the class implement some, but not all of the operations that you can perform

01:17.570 --> 01:18.860
on a traditional pointer.

01:21.470 --> 01:26.210
The template parameter is the type of the data that the member is pointing to.

01:26.690 --> 01:33.620
So if we have unique_ptr with parameter int, then the member is pointer to int. And unique_ptr is a

01:33.620 --> 01:34.520
move-only class.

01:35.000 --> 01:41.360
If we move a unique_ptr object, the allocated memory gets transferred from one object to another.

01:41.630 --> 01:44.210
So that transfers the ownership of the memory.

01:46.670 --> 01:50.450
To initialize a unique_ptr object. In C++11,

01:50.450 --> 01:57.650
we have to call new ourselves, and then use the return value to initialize the unique_ptr. So

01:57.650 --> 02:04.730
we can have something like that. So that it would be allocating memory for a single int object. And then having

02:04.730 --> 02:06.890
a unique_ptr which manages that memory.

02:07.880 --> 02:11.630
We could also have a fixed-size array, a we call new to create an array of ints.

02:12.170 --> 02:17.300
Usually, though, it is better to use either a standard array or a standard vector.

02:17.990 --> 02:23.690
The only situation where you would want to use this is if you need a fixed-size vector on the heap, because

02:24.080 --> 02:28.880
the library array cannot be allocated on the heap, and a vector does not have fixed size.

02:28.910 --> 02:30.890
It is possible to add or remove elements.

02:34.010 --> 02:40.850
In C++14, there is a make_unique() function, which is a bit like make_pair(), or our make_test() function.

02:41.660 --> 02:48.320
This will call new internally, to allocate the memory, and then it uses perfect forwarding to initialize

02:48.740 --> 02:52.610
the data in the memory, from the arguments to the call to make_unique().

02:53.420 --> 02:54.740
So we call make_unique().

02:54.740 --> 02:56.660
We need to give the data type again.

02:57.140 --> 02:58.640
And then the argument is 42.

02:59.150 --> 03:05.090
So that is going to allocate memory for an int, and then it will use perfect forwarding to initialize that

03:05.090 --> 03:06.860
int with the value 42.

03:08.480 --> 03:14.570
If we use the array form, then this will allocate an array of six ints, and we are going to use default

03:14.570 --> 03:15.950
initialization in this case.

03:19.570 --> 03:20.890
So let's try that out.

03:21.670 --> 03:26.080
So there is the C++11 form where we call new explicitly.

03:27.070 --> 03:33.070
And then the C++14 version, where we call make_unique(), which will do that for us.

03:34.510 --> 03:34.960
Okay.

03:37.610 --> 03:41.840
So we can perform most of the operations on a unique_ptr that we can on a traditional pointer,

03:42.260 --> 03:45.860
but not ones which involve copying, or modifying the pointer.

03:47.690 --> 03:53.540
And also, the unique_ptr distinguishes between a pointer to a single element and a pointer to an

03:53.540 --> 04:02.090
array. For example, we can dereference a pointer to a single object, but not a pointer to an array.

04:02.990 --> 04:08.420
We can use index notation with a pointer to an array, but not with a pointer to a single object.

04:09.320 --> 04:13.130
We cannot increment unique_ptrs, and we cannot assign them.

04:13.580 --> 04:14.960
Or call the copy constructed.

04:16.010 --> 04:20.660
We can move from one unique_ptr of the same type into another unique_ptr of

04:20.660 --> 04:21.320
the same type.

04:21.710 --> 04:26.030
But we cannot move from an array into a single object, or vice versa.

04:27.290 --> 04:30.530
One other thing we can do, is to assign to null pointer.

04:31.010 --> 04:33.850
This will cause the pointer member to be deleted.

04:33.860 --> 04:37.000
It will release the memory, and then the pointer will have the value

04:37.070 --> 04:37.360
null.

04:37.730 --> 04:40.760
So that will release the memory, and make the object invalid.

04:44.790 --> 04:45.170
Okay.

04:45.180 --> 04:47.670
And that is a default initialized value!

04:48.990 --> 04:53.950
As a very simple example of using a unique_ptr for a class or struct.

04:55.590 --> 04:57.460
We can call make_unique().

04:57.480 --> 05:01.770
We can pass an object of this class as the argument to make_unique().

05:02.610 --> 05:10.110
So this is going to create a temporary "Point" object, and then allocate memory to store it, and then perfect

05:10.110 --> 05:10.500
forward

05:10.500 --> 05:13.200
this point object into the allocated memory.

05:14.100 --> 05:18.120
We are using the auto keyword here, because that is a good place to use it.

05:18.390 --> 05:22.590
We know that we have a unique_ptr to a Point structure, so we do not need to spell it out.

05:23.820 --> 05:28.050
And then we can just dereference the members in this element, as we would with a normal pointer.

05:29.580 --> 05:31.200
And there we are, three and six.

05:32.100 --> 05:37.440
We can pass unique_ptr to a function, as an argument, by using pass by move.

05:37.980 --> 05:44.580
So we write a function which takes unique_pointer as argument, and then we have to cast it to an

05:44.580 --> 05:45.600
rvalue when we call it.

05:46.440 --> 05:51.540
So that will force the move constructor to be called, and not the copy constructor, which is deleted.

05:53.160 --> 05:59.310
So again, we have the same unique_ptr. And this time we pass it to the function. We call move() to

05:59.310 --> 06:00.330
cast it to an rvalue.

06:00.720 --> 06:03.960
And then this pointer will be moved into the function argument.

06:15.740 --> 06:18.830
And then if you try to access the data in this pointer...

06:20.710 --> 06:22.240
Well, there was a long pause there.

06:22.390 --> 06:26.110
I think that was the program actually crashing, because we were dereferencing a null pointer.

06:26.620 --> 06:29.370
So the pointer has been moved out of this object.

06:30.010 --> 06:33.670
So if we try to dereference it, we are now dereferencing a null pointer.

06:35.770 --> 06:39.130
We can also return a unique_ptr from a function.

06:40.390 --> 06:43.870
So we just return unique_ptr, with the type again.

06:45.880 --> 06:51.610
The object will be moved, from this local variable, into the function's return space. And then it will be

06:51.610 --> 06:53.470
moved into the caller's object.

06:54.520 --> 07:00.280
And by the way, we do not need to cast to an rvalue here, because the compiler will always move a return

07:00.280 --> 07:01.720
value if it is possible.

07:06.440 --> 07:09.110
So here we are, with our Point struct again.

07:10.250 --> 07:15.020
And then in our function, we are creating a unique_ptr to an object with this Point.

07:15.860 --> 07:16.880
And then we return it.

07:17.660 --> 07:20.870
We just put the name of the variable, and not move().

07:21.260 --> 07:26.690
In fact, if you do put a call to move in here, that can confuse the compiler, and it will generate code

07:26.690 --> 07:29.030
which is less optimized than if you left it alone.

07:29.870 --> 07:32.210
So the recommendation is just to return the variable here.

07:36.650 --> 07:37.160
And there we are.

07:39.530 --> 07:43.630
So we have called this function, point_ptr.

07:45.080 --> 07:46.580
We passed the values three and six.

07:47.030 --> 07:52.130
This function has created a unique_ptr, whose members have the values three and six.

07:52.520 --> 07:58.190
And then we returned the pointer, and then we dereference that, to get the values of the members.

07:59.240 --> 08:02.210
If we are not going to use this pointer again, then we can just return it.

08:02.210 --> 08:02.810
directly.

08:11.630 --> 08:12.410
And that still works.

08:13.460 --> 08:14.780
Okay, so that is it for this video.

08:15.110 --> 08:15.860
I will see you next time.

08:16.100 --> 08:17.780
Until then, keep coding!
