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.

1
2
3
4
std::string s1 = "Test";
//std::string&& r1 = s1;         // 错误:不能绑定到左值
std::string&& r1 = s1 + s1;      // okay :右值引用延长生存期
r1 += "Test";                    // okay :能通过到非 const 的引用修改

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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
func(int& rx);
func(int&& rx);

int x = 12;
int& lrx = x;
int&& rrx = 12;

func(12);           //调用 func(int&&)
func(lrx);          //调用 func(int&)
func(rrx);          //调用 func(int&)
func(std::move(x)); //调用 func(int&&)

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.

1
2
3
4
5
6
template <typename T>
decltype(auto) move(T&& param)
{
   using return_type = std::remove_reference<T>::type&&;
   return static_cast<return_type>(param);
}

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

1
2
3
4
5
6
7
template<typename ...Args>
void func_wrapper(Args&&... args){
    log("do something...");
    func(args...);
    //or
    func(std::move(args)...);
}

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.

1
2
3
4
5
template<typename ...Args>
void func_wrapper(Args&&... args){
    log("do something...");
    func(std::forward<Args>(args)...);
}

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.

1
2
3
4
template<typename T>
T&& forward(remove_reference_t<T>& param){
    return static_cast<T&&>(param);
}

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

1
2
3
4
template<typename T>
void f1(T&& param){
    f2(std::forward<T>(param));
}

When a left-valued reference is passed in, it is replaced with void f1(int&& &param), where T is int&, at which point std::forward is replaced with

1
2
3
4
5
6
7
int& && forward(remove_reference_t<int&>& param){
    return static_cast<int& &&>(param);
}
//折叠并计算后
int& forward(int& param){
    return static_cast<int&>(param);
}

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

1
2
3
4
5
6
7
int&& forward(remove_reference_t<int>& param){
    return static_cast<int&&>(param);
}
//折叠并计算后
int&& forward(int& param){
    return static_cast<int&&>(param);
}

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.