Hello again!
In this video, we are going to look at function arguments and move semantics.
We are going to try and find the fastest way of passing an object to a function, in C++ 11.
Before we do that, there are quite a few ways of passing objects, so let's make sure we fully understand
how they all work. When we have pass by value,
the copy constructor is called. The function has a variable which is a copy of the caller's
object. And the object which is passed is not modified, because it is a different object.
When we pass by const reference, the function has access to the caller's object, but it can only read
it, and it cannot modify it.
If we pass by non-const reference or by address, then the function has full access to the caller's
object, and it can modify it.
And finally, with pass by move, the move constructor is called.
This will move the data, from the passed object, into the function's object.
The function will now own that data.
And the caller's object is in a state where it cannot be used.
It can be destroyed. If you want to use it again,
you have to assign to it, or re-initialize it.
If we are passing an object to a function, before C++11, and the function is not going to modify it,
the usual advice is that you should pass by value.
If it's a built in type or a small object, maybe two or three members which are built-in types. And
anything larger should be passed by const reference.
And that was the most efficient way to pass objects, before C++11.
So does that still apply?
Is this still good advice in C++11?
Let's find out.
I think the best way to approach this is by looking at constructors.
Let's imagine that we have a class which has a string member, which is a moveable object.
So we could initialize this, using lvalues or rvalues, and we want to do this in the most efficient
way possible.
So how do we do that?
So we need to find out what goes in here.
Is it going to be by value? Or const reference?
Or maybe something else?
Let's find out.
Let's go through the possibilities.
So the first one is passing by const reference.
In both cases, lvalue or rvalue,
the passed object will be bound to this reference, or rather, the reference is bound to the object.
So this argument is going to be a reference to the caller's object.
Then we use that to initialize the data member of the class.
And that's going to be by copying because it's a value member.
So we have just one copy operation, where the argument is copied into the data member.
There is no copy for the actual argument, because these are bound by reference to the passed object.
The next one is pass by value, which can also be pass by move.
If we have an lvalue argument, then this is going to be passed by value.
So the object is going to be copied into this argument, and then the object will be copied into the
member.
So that is two copy operations,
if we have an lvalue argument.
For an rvalue, that will be moved into the argument, and then the argument will be copied into the
member.
So that is one move operation, and one copy operation.
If we are very cunning, we could look at this string argument. Aand we could say, this is actually a
copy of the caller's object, so we can do what we like with this.
So if we move from it, it is not going to cause the caller any problems.
So we could actually move into the data member of the class.
So if we have an lvalue, this will be copied into the argument, and then this copy can be moved into
the data member.
If we have an rvalue object passed, that can be moved into the argument, and then the argument can
be moved into the member.
So that is two move operations. And just to demonstrate you can actually do this...
Yep.
That compiles.
And then, finally, pass by rvalue reference.
And I hope that some of you thought about this.
If the passed object is an rvalue, then it can be moved into this argument, and then the argument
can be moved into the member.
Unfortunately, if the past object is an lvalue, then you are not allowed to do that.
You will get a compiler error.
So let's go through and add up all the work that's involved.
If we pass by const reference, there is one copy, for lvalue or rvalue objects.
If we pass by value/by move, there is two copies for an lvalue object and one move and one copy for
an rvalue object.
If we pass by value, and then move the argument into the data member, then there is one copy and one
move for an lvalue, and that is two moves for an rvalue object. And then, passing by rvalue reference.
There is one move with an rvalue.
So that is the winner
for an rvalue. Usually, moves are much quicker than copies. But it is not allowed at all for
lvalues.
So that is not the answer, or at least not the complete answer. For an lvalue,
the pass by const reference is the winner.
So which one do we choose? Or -
Well, who says we just have to choose one?
Why can we not have two?
So we could have one for lvalues and one for rvalues.
Does that work?
And yes, in fact, it does work.
We can have overloads, one which takes a const reference, and one which takes an rvalue reference.
If we pass an lvalue, then only this version can be called, because lvalues cannot bind to an
rvalue reference.
If we have an rvalue object passed, then either one of these could be called, because you can bind
an rvalue to a const lvalue reference.
However, the compiler will choose the best match, and the compiler will decide that
an rvalue reference is a better match than a const
reference to an lvalue.
So this will be called whenever an rvalue is passed.
So for lvalues, this requires one copy operation.
And for rvalues, we just need one move operation.
So.
That is the solution.
Well, there is one slight drawback.
If we have a construction with an empty body, it does not really matter that we have two functions.
If we have something which is not trivial, then we have to write the same code twice.
The function bodies will be exactly the same, but the function arguments will be different.
So that is a bit unsatisfactory.
But C++11 does actually provide a way around that, and we will look at that next time.
But for now, that is all.
I will see you later.
But until then, keep coding!