Move Semantics
Move semantics is a new concept introduced in C++11 for the case when an object is assigned to another object and is no longer used by itself. Instead of calling the copy constructor of the new object and then destroying the original object, with move semantics, the resources of the original object are “moved” to the new object, e.g. std::vector
assigns a pointer to an array to the new object, instead of requesting a new piece of memory and copying the contents of the original address This avoids costly copy operations. This semantics is especially critical when assigning temporary objects (right values).
In traditional C++ references can only be bound to left values, but in the mobile semantics it is necessary to distinguish between left-valued references, so &&
was introduced in C++11 to represent a reference to a right value and to extend the life of the object.
std::move
Although it is said that there is a right-value reference, a question arises, how to call the right-value version of the function? A right-value is a dying object with no name, and for int&& rx = 12
12
is the right-value, while rx
is just a reference to 12
, which itself is still a left-value. So the standard library provides the facility std::move()
to implement the transition.
With std::move()
it is possible to unconditionally transform left-valued arguments into right-valued references, but this function itself does not move the object, but only transforms it. Here is a possible implementation.
You can see that it’s just a type change, and there are no operations related to the move, not even any runtime performance loss, it’s the function that is really responsible for the move. Applying std::move()
is just to get the compiler to reload correctly to the moved version.
Perfect forwarding
As mentioned before the right value has no name, so in the following case
I want the external function func_wrapper
to be able to do something like write a log before calling the actual function. But the problem is that the function template accepts both left-valued and right-valued references, so how do I determine the argument type when making the call? If the call is made directly, then both are left-valued (even if right-valued is passed, the argument is derived as a right-valued reference and the right-valued reference is considered left-valued), and if all use std::move
then the passed left-valued is also moved. Writing a version for both kinds of references is not good for maintenance, but it is also not possible if the function itself accepts an arbitrary number of arguments.
std::forward
This leads to std::forward
perfect forwarding for conditional transformation, where passing in a left value forwards to a left value, and passing in a right value forwards to a right value.
Template type derivation collapses all right-valued references to left-valued references except for right-valued to right-valued references. This is the key to implementing std::forward
.
It is worth noting that the type T
must be given explicitly when using forwarding, as this information helps to identify the reference type.
Take the following function as an example
When a left-valued reference is passed in, it is replaced with void f1(int&& ¶m)
, where T
is int&
, at which point std::forward
is replaced with
Thus, the left-valued reference is forwarded. When a right-valued reference is passed in the replacement is void f1(int&& param)
, where T
is int
, so std::forward
is replaced with
The right-valued reference is forwarded, thus enabling a conditional transformation based on the arguments given by the caller to achieve a perfect forwarding of the arguments.
Summary
Move semantics is a newly introduced concept. std::move
implements an unconditional transition to the right value, and std::forward
implements a conditional transition to the right value only for right-valued references, both of which have no additional runtime consumption.
Generally std::move
is used for right-valued references, and std::forward
is used for generic references.