WEBVTT

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

00:01.110 --> 00:07.550
In this video, we are going to look at weak_ptrs. weak_ptrs are not actually smart pointers.

00:07.550 --> 00:13.080
They are something that you can use, when you are working with shared_ptrs. So they do belong in this

00:13.080 --> 00:13.680
section.

00:14.610 --> 00:19.860
The main point of having weak_ptrs is to provide a safe way of aliasing

00:19.890 --> 00:25.710
shared_ptrs. There is no provision for aliasing unique_ptrs, because, as the name

00:25.710 --> 00:27.750
suggests, they are meant to be unique!

00:27.750 --> 00:32.340
There is only one object which can access the memory. With a shared_ptr,

00:32.340 --> 00:34.650
there are several objects which can access the memory.

00:35.100 --> 00:36.870
And then we have the question of aliasing.

00:37.890 --> 00:43.770
There were some problems with the traditional way of aliasing, using raw pointers, so let's remind ourselves

00:43.770 --> 00:44.070
what those are.

00:44.070 --> 00:49.830
Here, we have allocated some memory, and we have a pointer to it.

00:50.610 --> 00:57.930
Then we have another pointer, which is an alias to this allocated memory, and then we release the allocated

00:57.930 --> 00:58.350
memory.

00:59.010 --> 01:05.790
So we still have this pointer here, and it is still in scope, and it is possible we could use it by accident.

01:06.360 --> 01:08.580
How do we know if this pointer is still valid?

01:09.000 --> 01:12.300
Well, in this code we can look, and it is fairly obvious that it is not valid.

01:12.750 --> 01:16.230
But if we have more complicated code, it is very hard to keep track of these things.

01:18.570 --> 01:23.100
So if we do run that, then we get a garbage result.

01:23.640 --> 01:24.240
We were lucky!

01:24.240 --> 01:25.440
We could have got a program crash.

01:29.250 --> 01:33.240
A weak_ptr is something that is bound to a shared_ptr object.

01:33.780 --> 01:37.920
So it is a bit like binding a reference to a normal variable, if you like.

01:39.150 --> 01:41.640
We start off by having a shared_ptr.

01:42.390 --> 01:43.110
So we call

01:43.110 --> 01:48.300
make_shared(). We have a reference of one, because there is just one object which is accessing the shared memory,

01:48.300 --> 01:49.230
which is this one.

01:49.950 --> 01:53.400
Then we bind the weak_ptr to this shared_ptr.

01:53.910 --> 01:57.780
The weak_ptr cannot directly access the shared_ptr's memory.

01:58.200 --> 02:00.750
So we still only have one object which can access the memory.

02:01.080 --> 02:02.850
So the reference count is still one.

02:03.900 --> 02:08.400
And then, when we release the memory, the reference counts goes down to zero.

02:10.160 --> 02:16.060
The only way that the weak_ptr can access the shared_ptr's memory is by being converted

02:16.060 --> 02:17.200
back to a shared_ptr.

02:17.710 --> 02:19.870
So, it is a bit like doing a cast, if you like.

02:20.380 --> 02:27.220
We have to convert it to the shared_ptr. And that will only be possible if the shared_ptr is

02:27.220 --> 02:27.970
still valid.

02:28.810 --> 02:35.230
So after this code, we can use the weak_ptr and convert it back, get the original shared_ptr.

02:35.800 --> 02:41.170
But once we release the memory from the shared_ptr, then we cannot do the conversion, so we cannot

02:41.170 --> 02:42.210
use the weak_ptr

02:42.230 --> 02:44.980
alias, after the shared_ptr has been released.

02:48.540 --> 02:51.900
To convert the shared_ptr, to recover the original object,

02:52.260 --> 02:59.220
we call the lock() member function of the weak_ptr. And this will return the original shared_ptr,

02:59.220 --> 03:03.690
if it is still valid. If it is no longer valid, then this will return null.

03:04.170 --> 03:05.520
So we could have a conditional.

03:06.000 --> 03:11.430
And if this is true, then we have a valid shared_ptr object, and we can safely use it.

03:12.150 --> 03:15.990
If it is false, then we got null back, and there is no shared_ptr object.

03:17.950 --> 03:24.200
There is another way of recovering the original shared_ptr, which is to initialize it. Again,

03:24.220 --> 03:28.360
this will only succeed if the weak_ptr is bound to a valid shared object.

03:29.350 --> 03:33.010
If it is not bound to a shared object, then this will throw an exception.

03:33.280 --> 03:34.630
bad_weak_ptr.

03:37.490 --> 03:43.070
The lock() member function is implemented as an atomic operation, again like the reference counter in the

03:43.070 --> 03:43.880
shared_ptr.

03:44.570 --> 03:47.390
This means it is thread-safe, but not very fast.

03:51.410 --> 03:53.060
So let's try our code again,

03:53.060 --> 03:57.890
using shared_ptrs and weak_ptrs. We create our shared_ptr.

03:57.890 --> 04:04.150
The reference count is one. We bind a weaker pointer to this shared_ptr.

04:04.580 --> 04:08.960
This does not affect the reference count, so the reference count is still one.

04:09.830 --> 04:16.070
This weak_ptr is now bound to a valid shared_ptr object, so we could use the weak_ptr here,

04:16.070 --> 04:17.450
to access the memory.

04:18.260 --> 04:20.720
Then we release the shared_ptr's memory.

04:21.010 --> 04:27.350
The reference count goes to zero, and the weak_ptr is no longer bound to a valid shared_ptr object.

04:30.070 --> 04:33.850
We call the lock() member function to get back the original shared_ptr.

04:34.510 --> 04:38.920
And if this succeeds, then we have a valid shared_ptr, and we can safely use it.

04:39.640 --> 04:43.170
If this fails, it returns null, and then we cannot use the shared_ptr.

04:44.860 --> 04:45.990
So what you think will happen?

04:50.010 --> 04:50.490
And there we are.

04:50.490 --> 04:51.870
The shared_ptr is not valid.

04:52.500 --> 04:57.750
So, because we released the shared_ptr's memory, we do not recover a valid shared_ptr.

04:59.310 --> 05:02.970
And we can also try the other version with the exception.

05:09.640 --> 05:11.190
So we have a try/catch block.

05:11.200 --> 05:15.880
We try to initialize the shared_ptr from the weak_ptr, in the try block.

05:16.690 --> 05:20.950
If this succeeds, then we can go on and safely use that shared_ptr.

05:22.090 --> 05:27.670
If the weak_ptr is not bound to a valid shared_ptr, then the exception is thrown and we go into

05:27.670 --> 05:28.330
the catch block.

05:28.690 --> 05:32.290
So there is no way there that we could use the invalid shared_ptr by mistake.

05:34.460 --> 05:34.970
And there we are.

05:35.330 --> 05:37.760
So that throws the bad_weak_ptr, an exception.

05:39.260 --> 05:39.950
Let's look

05:39.950 --> 05:42.680
now at a slightly more interesting example.

05:43.580 --> 05:47.780
We have a vector of shared_ptrs, then we print them out.

05:48.470 --> 05:50.660
Then we delete one of the members.

05:50.660 --> 05:57.170
We set the pointer to null. That will release the memory for that particular shared_ptr. And then we print out the

05:57.170 --> 05:57.770
vectors again.

05:59.570 --> 06:04.880
In the print() function, we just loop over the shared_ptr elements of this vector, and we

06:04.880 --> 06:06.080
dereference them and print them out.

06:07.280 --> 06:08.720
So what do you think will happen here?

06:09.590 --> 06:12.110
Bearing in mind that we have deleted one of these elements.

06:15.620 --> 06:19.430
So as you can see, when we got to the deleted element, we had a very long pause.

06:19.910 --> 06:23.510
And that was probably the program trying to recover from some kind of memory access error.

06:24.620 --> 06:29.180
So we need to check that the shared_ptr is valid before we dereference it.

06:29.870 --> 06:34.370
One way to do this is to use a weak_ptr as the element type in the loop.

06:35.300 --> 06:38.480
Then we call lock() to recover the original shared_ptr.

06:39.650 --> 06:44.270
If this succeeds, then we get a valid shared_ptr and we can safely dereference it.

06:45.050 --> 06:50.750
If this fails, then we get null, and we go into the else branch. And the rest of the code is the same.

06:53.150 --> 06:54.440
So what do you think will happen here?

06:56.500 --> 06:56.830
Yep.

06:57.070 --> 07:01.260
So when we get to the deleted elements, the weak_ptr lock() returns.

07:01.270 --> 07:01.600
null.

07:02.050 --> 07:03.430
And we do not do an

07:03.430 --> 07:03.930
unsafe

07:04.180 --> 07:04.840
dereference.

07:08.740 --> 07:12.340
So, practical applications of weak pointers. In a cache.

07:12.340 --> 07:18.040
For example, in your web browser, when you go to a website for the first time, the web browser has

07:18.040 --> 07:24.130
to fetch the HTML, the JavaScript, the images, and so on, over the Internet. And it will store them in

07:24.130 --> 07:27.610
its memory, but it also stores them in a cache.

07:28.480 --> 07:33.460
Then the next time you want to go to that page, the web browser will go to the cache and it can get

07:33.490 --> 07:37.500
all that data from the cache, instead of having to fetch to over the Internet again.

07:37.870 --> 07:40.840
So that will speed up your web browsing experience!

07:42.640 --> 07:46.690
One way to implement this is to store the data in shared_ptrs.

07:46.900 --> 07:49.180
So that is the data that has been retrieved over the Internet.

07:49.720 --> 07:53.230
And then in the cache there's a weak_ptr to the data.

07:53.560 --> 07:58.240
So when you want to go to a website, the browser will first look in the cache.

07:58.570 --> 08:03.010
It will call lock() on the object, which has the data for that website.

08:03.880 --> 08:08.920
And if there is data for that website, then this will return the shared_ptr.

08:10.390 --> 08:12.850
Sometimes this may not work. in that case,

08:13.180 --> 08:17.500
this will return null, and then the browser will need to fetch the original data over the Internet again.

08:18.820 --> 08:20.710
Okay, so that is it for this video.

08:21.400 --> 08:22.210
I will see you next time.

08:22.210 --> 08:24.610
But until then, keep coding!
