WEBVTT

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

00:01.140 --> 00:03.810
In this video, we are going to look at reference counting.

00:05.340 --> 00:11.190
Reference counting is a technique which allows different objects, usually of the same class, to access

00:11.190 --> 00:12.450
a shared resource.

00:13.500 --> 00:18.720
We use an integer to keep track of the number of objects which are sharing this resource.

00:19.230 --> 00:21.960
And that integer is known as a reference counter.

00:24.880 --> 00:30.820
Typically, the way this works is that the counter is zero, initially. Because there are no objects which

00:30.820 --> 00:32.170
are sharing the resource.

00:34.060 --> 00:39.580
If we get an object which uses the resource, so we have an object which is bound to this resource, then

00:39.580 --> 00:40.990
we have one object using it.

00:40.990 --> 00:42.330
So the count is now one.

00:42.340 --> 00:43.210
We increment it.

00:44.050 --> 00:48.400
If we add another object, then we now have two objects which are sharing the resource.

00:49.090 --> 00:51.730
So the counter is incremented again and becomes two.

00:52.810 --> 00:56.470
If an object stops using the resource, it is unbound.

00:56.890 --> 00:58.480
Then the counter is decremented.

00:58.810 --> 01:03.340
So we would go down, say, from having two objects, to having one object using the resource.

01:04.480 --> 01:08.800
And if all the objects are unbound, the counter will go down to zero.

01:09.220 --> 01:12.220
And that indicates that the resource is no longer being shared.

01:14.970 --> 01:18.360
We have this string class that we have been using through the course.

01:18.960 --> 01:24.330
We have this data member which is allocated memory, and now we are going to share that, and use reference

01:24.330 --> 01:25.710
counting to manage that.

01:26.460 --> 01:31.800
So we need to have an integer which is going to store the count, the number of objects which are sharing

01:31.800 --> 01:32.610
this memory.

01:33.540 --> 01:38.910
This integer has to be visible to all the objects which are sharing that memory, which are bound to

01:38.910 --> 01:39.120
it.

01:39.930 --> 01:43.050
And the usual way to do that, is to allocate the integer on the heap.

01:43.860 --> 01:49.440
So we are going to add a member to our class, which is going to be a pointer to the reference

01:49.440 --> 01:49.590
counter.

01:54.370 --> 01:59.650
The constructor for our class is going to allocate this counter, as well as allocating the memory.

02:00.550 --> 02:05.140
Initially the counter will have the value zero, because there are no objects which are bound to

02:05.140 --> 02:05.680
the memory.

02:06.490 --> 02:08.050
Then we allocate the memory.

02:08.050 --> 02:10.510
So now we are bound to the memory.

02:10.870 --> 02:12.490
So we need to increment the counter.

02:13.120 --> 02:14.940
So there is now one object using it,

02:14.950 --> 02:16.450
so the counter has a value of one.

02:17.920 --> 02:19.780
So the constructor will look like this.

02:20.200 --> 02:23.530
We allocate the counter, and initialize it to zero.

02:24.460 --> 02:29.530
Then we allocates the memory. And now we have one object which is using the memory, which is us.

02:30.130 --> 02:31.530
So we increment the counter.

02:36.800 --> 02:38.000
For the destructor,

02:38.000 --> 02:41.990
we need to think about what happens when an object of this class is destroyed.

02:42.530 --> 02:45.040
If it is destroyed, then it is no longer bound to the shared

02:45.050 --> 02:45.430
memory.

02:45.800 --> 02:47.870
So we need to decrement the counter.

02:51.410 --> 02:57.230
If the counter is not severe, then that means there are other objects which are still using the memory,

02:57.680 --> 03:00.290
and we must not release the memory.

03:01.400 --> 03:05.600
If the counter is zero, then we are the last object which was bound to that memory.

03:05.600 --> 03:08.690
And now when we go, there will be no-one using it.

03:09.200 --> 03:14.630
So it is up to us to release the memory, and also release the counter, because that is no longer needed.

03:17.700 --> 03:19.710
So the destructor will look like this.

03:19.710 --> 03:25.920
We decrement the counter. And then, if the counter is zero and we are "the last man standing", we need

03:25.920 --> 03:28.200
to release the memory and release the counter.

03:31.880 --> 03:37.610
For the copy constructor, we are initializing a new object to have the same values as the argument.

03:38.210 --> 03:43.380
So these strings s2, and s1 should have the same memory allocation and the same counter.

03:43.820 --> 03:46.700
So we now have another object which is bound to this memory.

03:47.840 --> 03:50.720
So the pointers need to have the same value, so we can just copy those.

03:51.080 --> 03:56.420
Shallow copy is good enough. But we need to increment the reference counter, because we now have another

03:56.420 --> 03:56.900
object.

04:01.290 --> 04:03.180
So the copy constructor will look like this.

04:03.210 --> 04:07.140
We copy the pointer members, and then we increment the counter.

04:10.240 --> 04:12.670
The assignment operator is a bit more complicated.

04:13.330 --> 04:16.570
We can do a shallow copy agenda and increment the counter.

04:17.170 --> 04:21.150
And if the two objects are sharing the same memory, then that is sufficient.

04:22.990 --> 04:29.500
If the two objects are bound to different memory, then the assigned-to objects is going to be unbound

04:29.860 --> 04:33.100
from its old memory, and bound to the new one.

04:33.670 --> 04:39.790
So we need to decrement the counter. And if that brings the counter down to zero, then we need to

04:39.790 --> 04:41.630
release the old shared memory.

04:42.130 --> 04:44.620
So this will be the same code as was in the destructor.

04:45.460 --> 04:51.550
So that is another example of the copy constructed logic consisting of the destructor's logic, followed

04:51.550 --> 04:56.560
by the constructor's logic. And we increment the counter.

04:56.830 --> 05:00.100
But we need to check first that we are not assigning to ourselves.

05:00.580 --> 05:03.190
Otherwise, we will have the counter off by one.

05:05.760 --> 05:11.350
So the full version of the assignment operator looks like this. We check whether the shared memory

05:11.400 --> 05:12.000
is the same.

05:12.570 --> 05:16.800
If it is different, then we need to have the same code as the destructor, to unbind

05:17.280 --> 05:19.260
The object that is being assigned to.

05:21.420 --> 05:27.210
Then we make a shallow copy of the members. And then we increment the counter, provided we are not doing

05:27.210 --> 05:28.050
a self-assignment.

05:28.350 --> 05:30.870
And finally, we return the assigned-to object.

05:34.320 --> 05:38.880
Or alternatively, we can use the copy and swap idiom, which is much simpler.

05:39.480 --> 05:40.800
We create a temporary object.

05:41.250 --> 05:47.190
We swap the members, and then we return the assigned-to object, which will destroy the temporary

05:47.220 --> 05:47.630
object,

05:47.640 --> 05:48.510
when this returns.

05:49.290 --> 05:54.090
The only thing we need to do for this is to add the counter member to the swap() function.

05:57.560 --> 05:58.910
So let's try this out.

05:59.420 --> 06:01.100
So we have the string function.

06:01.100 --> 06:04.820
We have the extra member for the counter. In the constructor,

06:04.820 --> 06:09.740
we allocate the counter and the memory, and then we increment the counter.

06:12.160 --> 06:16.840
I have put some debugging code in here, so we can see what is happening. In the destructor,

06:16.840 --> 06:22.390
we decrement the counter. And, if we are the last man standing, then we release the shared memory and

06:22.390 --> 06:22.930
the counter.

06:25.250 --> 06:31.190
In the copy constructor, we make a shallow copy and increment the counter. In the assignment operator,

06:31.190 --> 06:33.290
we check with the shared objects the same.

06:33.620 --> 06:38.480
If they are different, we unbind the assigned-to object, and then we copy the members.

06:39.230 --> 06:42.590
If we are not doing a self-assignment, then we increment the counter.

06:44.060 --> 06:45.560
Or we could use copy and swap.

06:47.580 --> 06:49.200
And the rest of the code is the same,

06:49.440 --> 06:52.740
more or less. I have added the counter to the print statement.

06:53.520 --> 06:58.170
And then in the main() function we create an object, copy construct, and then assign.

07:01.250 --> 07:06.080
So when we run this "a" and "b" are separate objects, with separate memory allocations.

07:06.830 --> 07:09.590
So they have different memory, and the counters are one.

07:09.950 --> 07:12.740
Each one of these is the only object that is using that memory.

07:13.970 --> 07:20.720
If we copy construct a new object "c" from "b", then "c" will have the same values as "b", they are both sharing

07:20.720 --> 07:21.380
the same memory.

07:21.380 --> 07:24.470
So now we have two objects, and the count is now two.

07:25.640 --> 07:33.610
If we assign "a" from "c", this is the old values of "a". a's memory is difference from "c", so we need to unbind

07:33.620 --> 07:36.770
"a". "a" was the last object which was using that memory.

07:36.770 --> 07:41.270
So we need to release it. And then we give "a" the same values that "c" had.

07:41.900 --> 07:45.140
So now we have "a", "b" and "c" which are all sharing the same memory.

07:45.530 --> 07:46.610
So the count is three.

07:48.630 --> 07:55.100
Then, when we exit the program, the destructors are called for "a", "b" and "c". c's destructor has the count

07:55.100 --> 08:01.440
to three, which it decrements to two. b's destructor decrements the count down to one, a's destructor

08:01.440 --> 08:06.600
decrements the count from 1 to 0, and then there are no objects which are sharing this memory.

08:06.840 --> 08:10.470
So a's destructor will release the shared memory and the counter.

08:14.980 --> 08:16.300
For move operators.

08:16.510 --> 08:23.140
Again, we have a situation where the existing approaches are almost but not quite sufficient. For the

08:23.140 --> 08:23.920
move constructor,

08:23.950 --> 08:30.190
we also need to add a shallow copy of the counter pointer, to the shallow copy of the shared memory.

08:30.940 --> 08:34.420
We need to set the counter to null in the moved-from object,

08:34.780 --> 08:41.650
as well as the data pointer, because all the contents of the object have been moved out of it.

08:42.220 --> 08:47.970
Now that we have set the counter to null, we have code all over the place, which is dereferencing the counter.

08:48.340 --> 08:53.260
So now we need to put in checks, to make sure that the counter is not null before we dereference it.

08:56.390 --> 09:01.760
In the move operations, we do not decrement the counter, because a move operation does not change

09:01.760 --> 09:03.020
the number of bound objects.

09:03.410 --> 09:06.860
All it does, is move the binding from one object to another.

09:07.340 --> 09:08.870
So the count remains the same.

09:11.230 --> 09:16.020
So here is the code that we had before, with the move operators added.

09:16.540 --> 09:22.870
So there is the move constructor, where we do a shallow copy of all the dat,a and then we set the pointers

09:22.870 --> 09:27.880
to null, in the object which is being moved from. In the move assignment

09:27.880 --> 09:29.590
operator, we just do copy and swap.

09:31.390 --> 09:36.160
And then, in the main() function, we move instead of copying.

09:42.710 --> 09:47.270
So again, we have the constructors, which create objects with different memory allocations.

09:48.020 --> 09:56.690
If we move from "b" into the new object "c", "c" has the values that used to be in "b", and "b" now has

09:56.690 --> 09:59.420
null, because all the contents have been moved out.

09:59.840 --> 10:03.110
"c" is now the only object which is using this memory allocation.

10:03.380 --> 10:04.700
So its count is one.

10:06.150 --> 10:09.600
Or if you like, the count has been moved, from "b", into "c".

10:10.860 --> 10:17.610
If we do move assignment, we call the move constructor to create the temporary object, and then, when this

10:17.610 --> 10:20.460
returns, the temporary object is destroyed.

10:22.610 --> 10:23.150
So there has

10:23.350 --> 10:25.280
the data that used to be in "a".

10:26.980 --> 10:33.370
Then "a" has the data that used to be in "c". Up here. And the data that was in "c" is now empty.

10:34.060 --> 10:37.480
So now "a" is the only object which is sharing this memory.

10:38.320 --> 10:44.020
When the destructors are called, "c" and "b" have null pointers, so nothing happens.

10:44.800 --> 10:50.740
The destructor for "a" has a count of one, which it decrements to zero, and then "a" will release the shared

10:50.740 --> 10:52.030
memory and the counter.

10:54.690 --> 10:55.020
Okay.

10:55.020 --> 10:56.220
So that is it for this video.

10:56.670 --> 10:57.540
I will see you next time.

10:57.540 --> 10:59.670
But until then, keep coding!
