WEBVTT

00:00:00.540 --> 00:00:03.380
Unique pointer is not the only smart pointer class

00:00:03.380 --> 00:00:05.550
provided by the standard library.

00:00:05.940 --> 00:00:09.450
We can also use shared pointers and weak pointers.

00:00:09.840 --> 00:00:12.150
Let's first talk about shared pointers.

00:00:12.540 --> 00:00:16.180
We said that unique pointer is named unique because it has

00:00:16.180 --> 00:00:18.750
an exclusive ownership of the resource.

00:00:19.140 --> 00:00:22.140
Shared pointers have non‑exclusive ownership.

00:00:22.150 --> 00:00:23.490
In other words,

00:00:23.500 --> 00:00:28.460
multiple shared pointers can share the ownership of a single resource in memory.

00:00:28.840 --> 00:00:33.460
Their declaration looks similar to the declaration of unique pointers.

00:00:33.840 --> 00:00:37.560
We use the templated shared pointer class to declare a new shared

00:00:37.560 --> 00:00:40.450
pointer, and just like with unique pointers,

00:00:40.450 --> 00:00:43.790
there is a complimentary make_shared function which allocates

00:00:43.790 --> 00:00:46.760
an object of this specific type on the heap.

00:00:47.140 --> 00:00:48.870
But unlike make_unique,

00:00:48.870 --> 00:00:53.520
this make_shared function has a more important role. Since shared

00:00:53.520 --> 00:00:57.750
pointers can share the same resource, copy semantics are allowed.

00:00:58.240 --> 00:01:00.520
So, we can do something like this.

00:01:00.530 --> 00:01:03.960
I will initialize a new shared pointer with another one.

00:01:05.140 --> 00:01:10.980
Now both of these pointers share the ownership of this single resource, but

00:01:10.990 --> 00:01:15.240
which one of these two will be responsible for deallocating this resource

00:01:15.240 --> 00:01:20.400
once they both go out of scope? Well it turns out that the shared pointer

00:01:20.400 --> 00:01:25.560
has two internal raw pointers, instead of one. The first one, of course,

00:01:25.560 --> 00:01:27.440
points to the resource itself,

00:01:27.450 --> 00:01:31.160
but the other one points to the so‑called control block.

00:01:31.940 --> 00:01:36.370
This control block is also an object on the heap, and among other

00:01:36.370 --> 00:01:39.160
things, it keeps track of the reference count.

00:01:39.540 --> 00:01:42.460
Reference count is the number of shared pointers which

00:01:42.460 --> 00:01:45.150
point to the same exact object in memory.

00:01:45.540 --> 00:01:47.780
So, when we declare this first pointer,

00:01:47.790 --> 00:01:52.830
the resource and the control block are allocated on the heap, and then

00:01:52.830 --> 00:01:56.840
this copy constructor will make a shallow copy of both these internal

00:01:56.840 --> 00:02:01.240
pointers, so this other shared pointer will point to the same resource

00:02:01.240 --> 00:02:03.060
and the same control block.

00:02:04.340 --> 00:02:09.139
The reference count on this shared control block will be incremented by one,

00:02:09.150 --> 00:02:13.160
because two pointers are now pointing to the same resource.

00:02:13.540 --> 00:02:16.150
So this is how shared ownership works.

00:02:16.160 --> 00:02:18.640
The reference count in the common control black

00:02:18.640 --> 00:02:21.060
holds the total number of owners.

00:02:21.740 --> 00:02:24.410
When one of these pointers goes out of scope,

00:02:24.420 --> 00:02:26.750
the reference count will decrease by one.

00:02:27.640 --> 00:02:30.840
Only the last remaining pointer is responsible for

00:02:30.840 --> 00:02:33.850
deallocating the owned resource from the heap.

00:02:34.640 --> 00:02:38.240
The make_shared function is useful because it can optimize

00:02:38.240 --> 00:02:42.440
performance by allocating the resource and the control block in a

00:02:42.440 --> 00:02:46.990
single block of memory, so only one heap allocation is necessary

00:02:46.990 --> 00:02:49.360
to initialize this shared pointer.

00:02:49.740 --> 00:02:53.980
If you just use new, then the resource and the control block will be

00:02:53.980 --> 00:02:59.060
allocated separately, so two potentially slow heap allocations.

00:03:00.240 --> 00:03:03.800
Since I already implemented my own version of the unique pointer,

00:03:03.800 --> 00:03:07.710
I thought that it will be useful to modify the custom smart pointer class

00:03:07.710 --> 00:03:11.160
to turn it into a simpler version of a shared pointer.

00:03:11.740 --> 00:03:14.250
This will help you visualize how the standard

00:03:14.250 --> 00:03:16.660
shared pointer class is implemented.

00:03:17.040 --> 00:03:22.660
You can download this script from the exercise files. I included the utility

00:03:22.660 --> 00:03:26.160
header because we will use the standard move function later.

00:03:26.840 --> 00:03:30.000
So, my version of the shared pointer is also a

00:03:30.000 --> 00:03:32.860
templated class with two pointer members.

00:03:33.340 --> 00:03:38.010
This second pointer should point to a control block, but to keep this

00:03:38.010 --> 00:03:42.570
simple, we will only implement a reference count, so my reference count

00:03:42.570 --> 00:03:45.550
will be an integer allocated on the heap.

00:03:46.040 --> 00:03:48.840
Default constructor will initialize the raw pointer to

00:03:48.840 --> 00:03:51.350
null and the reference count to zero.

00:03:51.740 --> 00:03:55.090
It is important that this reference count is allocated on the heap,

00:03:55.100 --> 00:03:59.630
because it will be shared by multiple smart pointers. Parameterized

00:03:59.630 --> 00:04:03.850
constructor takes in an address of the allocated resource, and it also

00:04:03.850 --> 00:04:08.240
sets the reference count to one, because this smart pointer is now

00:04:08.240 --> 00:04:09.960
pointing to this resource.

00:04:11.340 --> 00:04:15.920
I also defined a special get_ref_count function which will simply return the

00:04:15.920 --> 00:04:20.459
reference count integer. Copy constructor will make a shallow copy of the

00:04:20.459 --> 00:04:23.650
internal pointers from the other shared pointer.

00:04:24.040 --> 00:04:26.700
So now that both of these pointers point to the same

00:04:26.700 --> 00:04:29.600
resource and the same reference count, that reference

00:04:29.600 --> 00:04:31.960
count needs to be increased by one.

00:04:32.740 --> 00:04:36.810
I am also checking if this other pointer is pointing to something.

00:04:36.820 --> 00:04:37.770
If it isn't,

00:04:37.780 --> 00:04:42.620
then it makes no sense to increase the reference count. Copy

00:04:42.620 --> 00:04:45.960
assignment operator is a little bit more verbose.

00:04:46.340 --> 00:04:49.980
Since we are going to make this pointer point to another resource,

00:04:49.990 --> 00:04:54.160
we first need to remove its shared ownership from the current resource.

00:04:54.540 --> 00:04:59.200
We can do that by decreasing the reference count and then checking if it's 0.

00:05:00.240 --> 00:05:00.960
If it is,

00:05:00.960 --> 00:05:03.820
that means that this was the last share pointer which

00:05:03.820 --> 00:05:06.150
was pointing to this old resource.

00:05:06.840 --> 00:05:10.800
And if that's the case, then we need to deallocate that resource and the

00:05:10.800 --> 00:05:16.280
reference count from the heap. Then we can finally make a shallow copy again

00:05:16.280 --> 00:05:19.660
and increase the reference count of the new resource.

00:05:20.940 --> 00:05:25.830
Shared pointers also implement move semantics. Move constructor will

00:05:25.830 --> 00:05:30.060
make a shallow copy of the other shared pointer, but it will also

00:05:30.070 --> 00:05:34.590
invalidate that same pointer by setting both of its internal pointers

00:05:34.590 --> 00:05:39.840
to null. Move assignment operator has the same definition as the copy

00:05:39.840 --> 00:05:43.620
assignment operator, but instead of increasing the reference count of

00:05:43.620 --> 00:05:47.860
the new resource, we are invalidating the other shared pointer.

00:05:48.240 --> 00:05:51.640
This is because we are stealing both the resource and the

00:05:51.640 --> 00:05:55.110
reference count from that other pointer, so it makes no sense to

00:05:55.110 --> 00:05:58.950
increase the reference count because this other pointer is not

00:05:58.950 --> 00:06:01.050
pointing to anything anymore.

00:06:02.240 --> 00:06:07.040
And finally, the destructor implements the same logic we saw in the both

00:06:07.050 --> 00:06:11.230
assignment operators. When this shared pointer is deleted,

00:06:11.240 --> 00:06:14.480
the destructor will decrease the reference count and check if this

00:06:14.480 --> 00:06:18.060
pointer was the last one pointing to that owned resource.

00:06:18.440 --> 00:06:21.450
If it is, the resource and the control block will

00:06:21.450 --> 00:06:23.660
both be deallocated from the heap.

00:06:24.940 --> 00:06:28.820
So, that's how my custom shared pointer works, and I will make a

00:06:28.820 --> 00:06:33.610
short demonstration by using this simple complex class again. Inside

00:06:33.610 --> 00:06:37.280
of the main function, I declared a new shared pointer pointing to the

00:06:37.280 --> 00:06:39.150
complex object from the heap.

00:06:39.540 --> 00:06:43.080
Then I made a copy of this pointer two times by using the copy

00:06:43.080 --> 00:06:46.260
constructor for creating these two new pointers.

00:06:46.640 --> 00:06:51.560
I will output the reference count of this first pointer after each statement.

00:06:52.040 --> 00:06:52.710
Finally,

00:06:52.720 --> 00:06:58.250
I'm also allocating another complex object and assigning it to this c4 pointer.

00:06:58.640 --> 00:07:02.340
Then, I will transfer the ownership of this new object

00:07:02.350 --> 00:07:05.360
to the already existing c3 pointer.

00:07:06.740 --> 00:07:12.100
Now let's compile this. As you can see, everything works, because only

00:07:12.100 --> 00:07:16.060
two complex objects were actually constructed and then they were

00:07:16.060 --> 00:07:20.670
properly destructed later. The reference count is first one because

00:07:20.680 --> 00:07:25.200
only the pointer c is pointing to the first complex object. Then it

00:07:25.200 --> 00:07:29.060
increases by one after we share the resource with each one of these two

00:07:29.060 --> 00:07:30.560
new shared pointers.

00:07:30.940 --> 00:07:34.190
Finally, the ownership of the second complex number is

00:07:34.190 --> 00:07:36.540
transferred to the c3 shared pointer,

00:07:36.550 --> 00:07:39.330
which means that this pointer loses the ownership

00:07:39.340 --> 00:07:41.300
of the previous complex number,

00:07:41.310 --> 00:07:47.190
therefore, decreasing the reference count by one. I will now include the

00:07:47.190 --> 00:07:51.050
memory header to show you that this implementation is similar to the one of

00:07:51.050 --> 00:07:57.420
the shared pointer from the standard library. Replace all of the smart pointer

00:07:57.420 --> 00:08:02.400
declarations with the standard shared pointer, and replace new operator calls

00:08:02.400 --> 00:08:04.260
with the make_shared function.

00:08:04.840 --> 00:08:08.080
Shared pointers don't have a get_ref_count function,

00:08:08.080 --> 00:08:13.060
but you can get the reference count by utilizing the similar use_count function.

00:08:14.240 --> 00:08:16.490
So this is how shared pointers work.

00:08:16.500 --> 00:08:20.560
Be careful not to misuse them by assigning two shared pointers to the same

00:08:20.560 --> 00:08:25.520
resource without using copy semantics, because then the control block will not

00:08:25.520 --> 00:08:29.260
be updated, we would instead have two control blocks.

00:08:29.840 --> 00:08:33.120
I should also mention that since the release of C++20,

00:08:33.120 --> 00:08:38.059
you can point to raw arrays on the heap with both unique and shared pointers.

00:08:38.440 --> 00:08:43.020
But this is a really niche case, because we have vectors to do that in most

00:08:43.020 --> 00:08:46.560
situations, so, that's why I didn't include it in the lesson.
