Hello again!
In this video, we are going to add move operators to the class with RAII.
So this is the string-like class that we have been building up, throughout this course.
It allocates memory in the constructor, and releases memory in the destructor, which is the RAII idiom.
It has two members. which are both built-in types, and we are going to add a move constructor and a
move assignment operator to this class.
So what do we need to do? For the move constructor,
we need to move from the argument's members, into the members of the class that is being initialized. For
built-in types,
copying is equivalent to moving, so presumably we can just copy these arguments.
Here is our String classs, with the code we have seen before, for the constructor,
copy constructor, assignment operator
and the destructor.
In the destructor,
we are going to print out the address of the pointer that gets passed to delete.
There is the move constructor, in which we copy the argument members. Is that enough or do we need
to do anything else?
And that's not the correct thing to do!
The program has crashed.
If we look at the output from the destructor calls, the same address appears twice.
So we are trying to delete the same pointer twice.
And that is why the program crashed.
The problem is with this bit of code. If you think back to the copy constructor and the problems
we had with shallow copy, we ended up with two objects which have the same value for the pointer.
And then when the objects get destroyed, the pointer is deleted twice.
And this is really what is happening here.
So let's think about what we are trying to achieve. When we move from this argument object,
we are actually moving from the arguments members into these members.
So after that operation, these members are going to be invalid.
So let's do something about that.
If we have an invalid pointer, then we can express that by saying it is equal to the null pointer.
And for completeness, let's say that a size of zero represents an invalid size.
We also need to make sure that the final value of this argument
pointer
can be deleted safely.
That's not a problem because deleting the null pointer has no effect.
So this should now run without crashing.
Hurray!
So what are we doing? In the main function,
we create a couple of objects.
Then we call the copy constructor and then we call the move constructor, so we can compare the effect
of these two calls.
In the copy constructor, we are creating a new object initialized by "b", so it has the same value for
the size.
The address pointer is different, because this has its own memory allocation. With the move constructor,
on the other hand, the new object ends up with the same values that "b" originally had, and "b" is now invalidated.
And you can see there that the null pointer is deleted, without any ill effects. For the move assignment
operator, we have two options.
We can write something which is similar to the traditional copy assignment operator. So we check for
self-assignment, then we delete the original memory allocation, and then we get the memory.
In this case, we do not allocate a new memory allocation.
We steal the memory allocation from the argument object. And then we need to invalidate the argument
object.
So this is really the usual thing.
We have the destructor code followed by the constructor code.
In this case, it is the move constructor code and not the copy constructor code.
So there is our move assignment
operator. And again, we return by lvalue reference.
In the main function, we create a couple of objects and then we call the copy assignment operator and
the move assignment operator.
So with copy assignment, the object which is assigned has the same size member.
It has a different address because, again, it allocates its own memory allocation. With the move assignment,
so we are move assigning from this object.
The assigned-to object ends up with the same data members that the original object had, that we are assigning
from, and the assigned-from object is now invalidated.
The alternative is to use something similar to copy-and-swap, which is easier to write but a bit harder
to understand.
So we are actually going to use move-and-swap.
But the basic idea is the same.
We create a temporary object which is initialized from the argument.
Then we swap the members of this temporary object and the object we are assigning to, and then we return
the assigned-to object.
When the destructor is called, this temporary object is destroyed, and that will release the memory
allocation from the original assigned-to object.
We are using the move constructor here to initialize the temporary object, so we need to cast the argument
to an rvalue.
This will call the move constructor, which will set the argument's pointer to null, and the temporary object
will have the original pointer from the argument.
Then we swap them around.
The temporary object now has the original pointer from the "this" object, that we are assigning to and "this"
now has the pointer from the argument.
And then, when we return, the destructor will release the original pointer from the "this" object.
So there is our move-and-swap operator, and we have the same main() function again.
So the output is a bit different for the move assignment
operator.
We have the move constructor call, for this initialization of the temporary object, and then we have
the destructor call, when the temporary object is destroyed.
So the address that is released is this one, which was the original pointer in the object that is being
assigned to, this object
"c".
So the object "c" ends up with the data from the original object "a", the members, and "a" is invalidated.
Okay, so that is it for this video.
I will see you next time.
But until then, keep coding!