Hello again! In this video, we are going to look at move-only types and RAII. In C++, you can create a move-
only type. So this is something which can be moved but not copied. And to do that, you declare the copy
constructor and the copy assignment operator as deleted, and then you implement the move operators and
the other member functions in the usual way.
Let's try this out with the Test class that we were using in an earlier video.
So we have the copy constructor and the copy assignment operator declared as "=delete".
This does not mean that the functions are non-existent!
It means that the compiler can decide that these are the best match.
But when they actually get called, there is a compiler error.
So these are functions which exist and do not exist at the same time.
They are "Schroedinger's cat" functions, if you like.
Then we have the same move constructor and move assignment operator as we had before.
We also have the same main() function, but with the copy constructor and the copy assignment operator
calls commented out.
So if we run this, we get the same results for the move operators as we had before.
We still get this statement being optimized out, which is still annoying me!
If I uncomment the copy constructor call, what will happen?
We get a compiler error. "Attempting to reference a deleted function".
And if I uncomment the copy assignment operator, then we get the same error again.
For some reason, it is displayed differently.
Is this something which is useful to do?
Yes, it is.
In fact, the C++ standard library has several classes which are move-only.
For example, fstream and iostream can be moved, but cannot be copied.
There are some move-only classes in multi-threading, which we are not looking at in this course.
And also the so-called "smart pointer" classes, which we will be looking at in the next section.
Those are move-only as well.
These types follow the RAII idiom, which means that only one object can own be given a resource instance at
any one time.
The object will acquire ownership of the resource in its constructor, and it will release the ownership
in the destructor.
We can also use move semantics to transfer the ownership of this resource, from one object to another.
So if we look at fstream as an example, this has something called a "file handle" as the data member.
So this is something that is used by the operating system, to communicate with the file on disk.
The fstream constructor will open the file.
It will be given a handle by the operating system.
The destructor will close the file, so it will return to the handle back to the operating system.
The fstream object cannot be copied, because that would mean there are two objects which are both
going to try to close the file, which is not desirable.
But we can move the object.
So this will cause the file handle to be transferred, from one object to another.
So the object which is moved from, no longer owns that file handle. It now has a null or invalid file
handle. The object which is moved into becomes the owner of the file handle, and it is responsible
for releasing that file handle when the file is no longer needed.
So when the destructor is called for that object, it will close the file.
Unless of course the file handle has been moved into another object in the meantime.
There is one issue with move-only objects in C++11, which comes into effect if we try to use them
in lambda expressions.
If we have an fstream object, for example, and we want to capture this in a lambda expression,
we cannot capture this by value, because that does not compile.
I have now got a main() function which creates an object of this class, and a lambda expression which tries to
capture it by value.
So, what will happen here?
And, right, it does not compile, because we tried to call the copy constructor, which is deleted.
So it is the same problem again.
We can capture the file stream by reference.
And that will give us access to the file, but it does not give us the ownership.
If we want to be able to transfer ownership into the lambda expression, then we need a capture
by move.
And that is not supported in C++11.
However, it is supported in C++14.
It is a little bit involved,
though. C++14 has generalized lambda capture, so this means we can create lambda-local variables.
You remember, we do this by declaring the variable inside the capture specifier.
So this variable "lfs" is going to be a local variable to the lambda expression body.
The type of this variable "lfs" will be deduced from its initializer.
If we make this initializer an rvalue, then this is going to be initialized using the move constructor.
So this will move the file handle, from this "fs" object in scope, into the local variable in the expression.
So that is how you do capture by move.
So in this expression, the "lfs" variable will be deduced as an fstream.
The move constructor will be called to initialize it from the file stream argument, from the outer scope
variable, and this variable in the lambda expression will take ownership of the file handle.
And this was actually the second most requested feature in C++ 14.
So obviously there is is a demand for it!
So let's have an example of that.
I am going to capture this local variable by move.
So this is a vector of strings.
I am also going to catch it by reference, so we can see what difference that makes.
After each of these captures, we are going to print out the number of elements in the vector.
I have put a pair of brackets after the lambda definition, so that will cause the lambda expression
to be called. When the lambda expression captures by reference,
it will have a reference to this vector of strings, but it does nothing to alter it.
So we should expect to see 5 as the number of elements, when the lambda expression returns. With the
capture by move,
This scope variable is going to be moved into the lambda-local variable.
So this is now the owner of those strings, and the original scope vector is going to be empty.
So when the lambda returns, we expect to see 0 for the number of elements.
And, what do we get? So we get 5 for capture by reference and 0 for capture by move.
Okay.
So that is it for this video.
I will see you next time.
But until then, keep coding!