WEBVTT

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

00:01.110 --> 00:07.590
In this video, we are going to look at shared_ptr. The shared_ptr type was introduced in C++11,

00:07.590 --> 00:13.890
in the library. It uses reference counting, so we can have different shared_ptr objects which

00:13.890 --> 00:15.660
share the same memory allocation.

00:16.230 --> 00:20.370
When we copy or assign one of these objects, there are no memory operations.

00:20.850 --> 00:23.070
Instead, it just increments the reference counter.

00:23.850 --> 00:27.120
If one of the objects is destroyed, then the reference counter is decremented.

00:27.120 --> 00:28.140
in the destructor.

00:28.560 --> 00:33.810
And if that was the last shared_ptr object which was sharing that memory, then the counter is

00:33.810 --> 00:36.930
equal to zero, and the allocated memory is released.

00:39.460 --> 00:39.750
shared_ptr

00:39.770 --> 00:45.790
has a private situation member, which points to the allocated memory, and it also has another

00:46.090 --> 00:49.870
private data member, which points to the so-called "control block".

00:51.390 --> 00:55.350
This control block contains the reference counter, and a few other things.

00:56.700 --> 01:01.020
So a shared memory object will have the memory allocation and the control block.

01:05.360 --> 01:05.770
The shared_ptr

01:05.780 --> 01:10.580
class is defined in the <memory> header. To create a shared_ptr object,

01:10.970 --> 01:15.080
we can pass a pointer to the constructor, or we can call make_shared().

01:15.770 --> 01:17.930
So it is just like creating a unique_ptr.

01:18.920 --> 01:23.420
We can also initialize a ahared_ptr object by moving a unique_ptr into it.

01:24.260 --> 01:29.510
So we have this unique_ptr, which is a single object, which has total ownership of the memory.

01:30.170 --> 01:36.560
And we can transfer that ownership to shared_ptr objects, which share that ownership amongst themselves.

01:38.370 --> 01:43.620
So this could be useful, if you are working with a factory function which returns a unique_ptr, and

01:43.620 --> 01:44.910
you actually want a shared_ptr.

01:45.960 --> 01:51.600
And also, if you start writing a program using unique_ptrs, and you realize later on that you actually

01:51.600 --> 01:54.180
want shared_ptrs, then you can just convert them.

01:55.170 --> 01:56.500
But you cannot go the other way around.

01:56.820 --> 02:04.770
You cannot convert a shared_ptr to a unique_ptr. make_shared() is available in C++11, unlike

02:04.770 --> 02:07.800
make_uunique(), which they forgot about until C++14!

02:08.760 --> 02:12.000
So we need to give the type of the object in memory.

02:12.420 --> 02:18.270
And we pass the constructor arguments. And when we call make_shared(), this will perform a single call

02:18.270 --> 02:24.600
to new. And that allocates the shared memory and the control block, in one single contiguous block of

02:24.600 --> 02:24.990
memory.

02:27.340 --> 02:34.480
If we pass a pointer which was returned by new, then the shared_ptr constructor will call new again,

02:34.810 --> 02:36.580
to allocate the control block.

02:37.120 --> 02:45.370
So that is two calls to new. Two memory allocations. And with most memory managers in most operating systems, these

02:45.370 --> 02:50.770
two allocations could well be in different parts of memory, which means that accessing them is less

02:50.770 --> 02:51.280
efficient.

02:51.970 --> 02:56.140
Modern hardware is optimised for accessing memory objects which are next to each other.

03:00.580 --> 03:05.410
And then the copy instructor, assignment operator and the move operators work just like the example

03:05.410 --> 03:06.220
in the last video.

03:06.910 --> 03:09.820
So if we copy, then the reference counter is incremented.

03:10.540 --> 03:15.910
If we assign, then the reference counter is incremented in the object that is being assigned from,

03:15.910 --> 03:19.300
and decremented in the object that is being assigned to.

03:20.140 --> 03:24.490
And that could cause the memory to be released, if the counter goes down to zero.

03:25.420 --> 03:31.030
And then, after this assignment, "p3" will now have the same memory pointer and control block pointer

03:31.060 --> 03:31.780
as "p1".

03:33.300 --> 03:38.910
If we move from a shared_ptr object, the memory allocation pointer is set to null.

03:39.660 --> 03:43.110
The reference counter in "p4" is the same as it was in "p1".

03:43.650 --> 03:50.430
So, if you like, this target object will steal the memory allocation and the control block from the argument.

03:53.390 --> 03:57.950
shared_ptr supports the same observations, more or less, that unique_ptr does.

03:58.490 --> 04:04.970
So we can dereference a shared_ptr to access the memory, we can set it to null to delete the memory.

04:06.020 --> 04:09.050
We can also assign and copy them, as well as moving them.

04:09.590 --> 04:11.750
But we still cannot do pointer arithmetic.

04:18.120 --> 04:23.130
If we have code which uses threads, then there is a possible data race.

04:23.700 --> 04:28.950
If we have two threads, it is possible that they could try to modify the reference counter concurrently.

04:29.760 --> 04:31.800
In fact, the reference counter is "atomic".

04:32.160 --> 04:36.420
So copying or assigning shared_ptr objects is thread-safe.

04:37.380 --> 04:39.900
However, there is no protection for the actual allocated memory.

04:39.900 --> 04:46.560
So if you want to access that, within threads, you must provide your own protection, typically by locking

04:46.560 --> 04:46.830
a mutex.

04:48.330 --> 04:51.150
In C++20, there is an atomic shared_ptr.

04:52.650 --> 04:56.400
The drawback of this is that operations on atomic variables take much longer,

04:56.880 --> 04:58.140
even if your code is not threaded.

04:59.370 --> 05:03.030
So, should you replace all your unique_ptrs with shared_ptrs?

05:03.750 --> 05:05.070
And the answer is, probably not.

05:06.150 --> 05:07.680
unique_ptr is very efficient.

05:07.710 --> 05:13.560
There's no more overhead to using a unique_ptr than there is to using a traditional pointer with

05:13.560 --> 05:16.770
new. The shared_ptr has more overhead.

05:17.310 --> 05:22.830
The control block has to be initialized when you create the first shared_ptr object. And also, if

05:22.830 --> 05:28.620
you copy, assign or destroy a shared_ptr object, the reference counter needs to be updated, which

05:28.620 --> 05:29.850
is a slow operation.

05:31.670 --> 05:35.810
And as we said, you can convert a unique_ptr to shared_ptr, but not vice-versa.

05:36.590 --> 05:38.480
So with unique_ptrs, you can change your mind.

05:39.230 --> 05:43.970
So the recommendation is, only use a shared_ptr when you need the sharing or if it adds value.

05:44.840 --> 05:48.130
Some people say that you should use a shared_ptr all the time, because it is easier.

05:48.140 --> 05:51.380
And yes, you can do that, if you are not worried about efficiency.

05:53.540 --> 05:59.000
So, where are shared_ptrs useful? If you have different objects which need to have access to the

05:59.000 --> 06:00.890
same area of heap memory.

06:01.880 --> 06:07.010
For example, if we have a large document, which takes a lot of memory to store, and it has a lot of duplicated

06:07.010 --> 06:12.500
words, we can reduce the memory usage, by storing the words in a shared_ptr.

06:13.100 --> 06:18.710
So we just have one memory allocation for each word, as opposed to having a different memory allocation

06:18.980 --> 06:20.780
each time the word appears in the document.

06:21.380 --> 06:27.380
And if we have a web browser where we have several tabs which have the same image, so these could be

06:27.380 --> 06:31.100
a company logo or maybe avatars in a discussion forum.

06:32.450 --> 06:36.980
Again, we could save memory by using a shared_ptr, so we only store the image once.

06:38.480 --> 06:40.010
Okay, so that is it for this video.

06:40.490 --> 06:41.360
I will see you next time.

06:41.600 --> 06:43.640
Until then, keep coding!
