Transfer and perfect forwarding

Posted by phpian on Sun, 28 Nov 2021 05:42:12 +0100

[transferred from]:
Author: Su Bingyu
Link: https://subingwen.cn/cpp/move-forward/
Source: Da C who loves programming

1. std::move

An R-value reference is added in C++11, and an R-value reference cannot be initialized with an l-value. If you want to initialize an R-value reference with an l-value, you need to use the std::move() function to convert an l-value to an R-value using the std::move method. Using this function does not move anything, but has the same move semantics as the move constructor. It transfers the state or ownership of an object from one object to another, but without memory copy.

In terms of implementation, std::move is basically equivalent to a type conversion: static_ cast<T&&>(lvalue);, The function prototype is as follows:

template<class _Ty>
_NODISCARD constexpr remove_reference_t<_Ty>&& move(_Ty&& _Arg) _NOEXCEPT
{   // forward _Arg as movable
    return (static_cast<remove_reference_t<_Ty>&&>(_Arg));
}

The method of use is as follows:

#include <iostream>

class Test
{
public:
    Test() {};
    ~Test() {};
};

int main()
{
    Test t;
    Test&& v1 = t;            // ERROR: 'initializing': cannot convert from 'Test' to 'Test &&', you cannot bind an lvalue to an rvalue reference
    Test&& v2 = std::move(t); // ok, !! Usually, the move semantics is executed for t, which means that the t variable is no longer used!!
    return 0;
}

Assuming that a temporary container is large and needs to be assigned to another container, you can perform the following operations:

std::list<std::string> ls;
ls.push_back("hello");
ls.push_back("world");
......
std::list<std::string> ls1 = ls;        // Copy required, inefficient
std::list<std::string> ls2 = std::move(ls);

If std::move is not used, the cost of copying is high and the performance is low. Using std::move has almost no cost, but only converts the ownership of resources. If there is a large heap memory or dynamic array inside an object, std::move can be used to transfer data ownership very conveniently. In addition, we can also write corresponding mobile constructors (T:: t (T & & other)) and assignment functions with mobile semantics (T & & T:: operator = (T & & RHS)) for classes to reuse resources as much as possible when constructing objects and assigning values, because they all receive an R-value reference parameter.

2. std::forward

The right value reference type is independent of the value. When an right value reference is used as a formal parameter of a function parameter, when the parameter is forwarded to other internal functions within the function, it becomes an lvalue instead of the original type. If you need to forward to another function according to the original type of parameters, you can use the std::forward() function provided by C++11. The function implemented by this function is called perfect forwarding.

// Function prototype
template <class T> T&& forward (typename remove_reference<T>::type& t) noexcept;
template <class T> T&& forward (typename remove_reference<T>::type&& t) noexcept;

// What it looks like after streamlining
std::forward<T>(t);
  • When t is an lvalue reference type, t is converted to an lvalue of type T
  • When t is not an lvalue reference type, t is converted to an lvalue of type T

The following is an example to demonstrate the use of forward:

#include <iostream>

template<typename T>
void printValue(T& t)
{
    std::cout << "l-value: " << t << std::endl;
}

template<typename T>
void printValue(T&& t)
{
    std::cout << "r-value: " << t << std::endl;
}

template<typename T>
void testForward(T&& v)
{
    printValue(v);
    printValue(std::move(v));
    printValue(std::forward<T>(v));
    std::cout << std::endl;
}

int main()
{
    testForward(520);
    int num = 1314;
    testForward(num);
    testForward(std::forward<int>(num));
    testForward(std::forward<int&>(num));
    testForward(std::forward<int&&>(num));
    return 0;
}

The printed results of the test code are as follows:

l-value: 520
r-value: 520
r-value: 520

l-value: 1314
r-value: 1314
l-value: 1314

l-value: 1314
r-value: 1314
r-value: 1314

l-value: 1314
r-value: 1314
l-value: 1314

l-value: 1314
r-value: 1314
r-value: 1314
  • testForward(520); The formal parameter of the function is an undetermined reference type T & &, and the actual parameter is an R-value. After initialization, it is deduced as an R-value reference

    • printValue(v); For the named right value V, the compiler will treat it as an lvalue, and the argument is an lvalue
    • printValue(std::move(v)); The named R-value compiler will treat it as an l-value, convert it to an R-value through move, and the argument is an R-value
    • printValue(std::forward(v)); The template parameter of STD:: forward is a right value reference, and finally an right value is obtained. The actual parameter is a ` ` right value`
  • testForward(num); The formal parameter of the function is an undetermined reference type T & &, and the actual parameter is an lvalue. After initialization, it is deduced as an lvalue reference

    • printValue(v); Argument is lvalue
    • printValue(std::move(v)); The left value is converted to the right value through move, and the argument is the right value
    • printValue(std::forward(v)); The template parameter of STD:: forward is an lvalue reference. Finally, an lvalue reference is obtained, and the actual parameter is an lvalue
  • testForward(std::forward(num)); The template type of STD:: forward is int, which will eventually get an R-value. The formal parameter of the function is an undetermined reference type. T & & gets an R-value reference type after being initialized by the R-value

    • printValue(v); For the named right value V, the compiler will treat it as an lvalue, and the argument is an lvalue
    • printValue(std::move(v)); The named R-value compiler will treat it as an l-value, convert it to an R-value through move, and the argument is an R-value
    • printValue(std::forward(v)); The template parameter of STD:: forward is a right value reference, and finally an right value is obtained, and the actual parameter is a right value
  • testForward(std::forward<int&>(num)); The template type of STD:: forward is int &, and finally an lvalue will be obtained. The formal parameter of the function is an undetermined reference type. T & & after being initialized by the lvalue, an lvalue reference type will be obtained

    • printValue(v); Argument is lvalue
    • printValue(std::move(v)); The left value is converted to the right value through move, and the argument is the right value
    • printValue(std::forward(v)); The template parameter of STD:: forward is an lvalue reference. Finally, an lvalue is obtained, and the actual parameter is lvalue
  • testForward(std::forward<int&&>(num)); The template type of STD:: forward is int & &, and finally an R-value will be obtained. The formal parameter of the function is an undetermined reference type. T & & gets an R-value reference type after being initialized by the R-value

    • printValue(v); For the named right value V, the compiler will treat it as an lvalue, and the argument is an lvalue
    • printValue(std::move(v)); The named R-value compiler will treat it as an l-value, convert it to an R-value through move, and the argument is an R-value
    • printValue(std::forward(v)); The template parameter of STD:: forward is a right value reference, and finally an right value is obtained, and the actual parameter is a right value

Topics: C++