C++ review notes--perfect forwarding, use of std::move and std::forward

Table of contents

1--perfect forwarding

1-1--Example code analysis

2--Use of std::forward

2-1--std::forward source code

2-2--std::forward vernacular summary

3--The use of std::move

3-1--The source code of std::move

3-2--std::move vernacular summary


1--perfect forwarding

Why do you need perfect forwarding ?

        C++ Primer has such a paragraph about forwarding ( P612 ): Some functions need to forward one or more of their actual parameters to other functions with their types unchanged . In this case, we need to maintain all the properties of the forwarded actual parameters ;

        In plain language, when we want to forward certain parameters, we don’t want this parameter to be changed during the forwarding process (such as changing from an rvalue to an lvalue), and perfect forwarding can realize the above vision;

1-1--Example code analysis

        Combine the following code to explain an example of perfect forwarding: code reference

#include <iostream>

template<typename T>
void print(T & t){ // 只接受左值
    std::cout << "Lvalue ref" << std::endl;
}

template<typename T>
void print(T && t){ // 万能引用,可以接受左值和右值
    std::cout << "Rvalue ref" << std::endl;
}

template<typename T>
void testForward(T && v){ 
    print(v);
    print(std::forward<T>(v)); 
    print(std::move(v)); 

    std::cout << "======================" << std::endl;
}

int main(int argc, char * argv[]){

    int x = 1;
    testForward(x); // 传递左值
    testForward(1); // 传递右值
}

        The result of running the code is as follows:

Lvalue ref
Lvalue ref
Rvalue ref
======================
Lvalue ref
Rvalue ref
Rvalue ref
======================

Code analysis :

For the first testForward(x) execution:

        ① x itself is an lvalue, when passed to the function testForward(T && v), T will be inferred as int & ( inference analysis reference notes: universal reference and reference folding ); the function template is equivalent to  testForward(int & v ) ; (reference folding occurs  T && → int & && → int & ); when executing the first print(v), v is an lvalue , so the first print() function is called first ;

        ② When executing the second print(std::forward<T>(v)), v is an lvalue, and the value returned by std::forward<T>(v) is still an lvalue (the source code will be combined later Analyze the use of std::forward ), so the first print() function is called first ;

        ③ When the third print(std::move(v)) is executed, v is an lvalue, and the value returned by std::move(v) is an rvalue ( the use of std::move will be analyzed in conjunction with the source code later ), so only the second print() function can be called ;

For the execution of the second testForward(1):

        ① The literal value 1 itself is an rvalue , when passed to the function testForward(T && v), T will be deduced as int ; the function template is equivalent to  testForward(int v) ; when executing the first print(v), Since v uses a variable ( with an address ) to store the value 1 at this time, v is an lvalue , and the first print() function will be called first ;

        ② When the second print(std::forward<T>(v)) is executed, v is an lvalue, since T is deduced as an int , the value returned by std::forward<T>(v) is a rvalue (combined with source code analysis), so only the second print() function can be called ;

        ③ When the third print(std::move(v)) is executed, v is an lvalue, and the value returned by std::move(v) is an rvalue (combined with source code analysis), so only the first Two print() functions;

        The above example is to illustrate that the std::forward<T>() function can return a value according to the difference of T, and at the same time, it can well maintain the original type attributes ; the following will combine the source code analysis of std::forward and std: :move function;

2--Use of std::forward

2-1--std::forward source code

/**
   *  @brief  Forward an lvalue.
   *  @return The parameter cast to the specified type.
   *
   *  This function is used to implement "perfect forwarding".
   */
  template<typename _Tp>
    _GLIBCXX_NODISCARD
    constexpr _Tp&&
    forward(typename std::remove_reference<_Tp>::type& __t) noexcept
    { return static_cast<_Tp&&>(__t); }

  /**
   *  @brief  Forward an rvalue.
   *  @return The parameter cast to the specified type.
   *
   *  This function is used to implement "perfect forwarding".
   */
  template<typename _Tp>
    _GLIBCXX_NODISCARD
    constexpr _Tp&&
    forward(typename std::remove_reference<_Tp>::type&& __t) noexcept
    {
      static_assert(!std::is_lvalue_reference<_Tp>::value,
	  "std::forward must not be used to convert an rvalue to an lvalue");
      return static_cast<_Tp&&>(__t);
    }

Simply combine the source code of the first template to analyze the above code example:

        ① For std::forward<T>(v), when v is an lvalue and T has been inferred to be int & , std::forward<T>(v) is equivalent to std::forward< int&>(v); that is, for the following source code, the incoming _Tp is int& ;

  template<typename _Tp>
    _GLIBCXX_NODISCARD
    constexpr _Tp&&
    forward(typename std::remove_reference<_Tp>::type& __t) noexcept
    { return static_cast<_Tp&&>(__t); }

        For the above source code, you first need to know that the result of std::remove_reference<int&>::type is int (see C++ Primer Page 605 for details), and _Tp&& undergoes reference folding : _Tp&& → int& && → int& , then the source code is equivalent to constexpr int& forward(int& __t) , the returned type is int& , that is, the returned type corresponds to an lvalue reference , and the returned value is an lvalue ;

        ② For std::forward<T>(v), when v is an lvalue and T has been deduced as an int  ,

std::forward<T>(v) is equivalent to std::forward<int>(v); that is, for the above source code, the incoming _Tp is int ; the result of std::remove_reference<int>::type Still int , the source code is equivalent to constexpr int&& forward(int& __t) , and the returned type is int&& , that is, the returned type corresponds to an rvalue reference , and the returned value is an rvalue ;

        ( There may be doubts here? Obviously the returned type is an rvalue reference. Although an rvalue reference can only bind an rvalue, it is essentially an lvalue. Why is it returning an rvalue?) (Answer : First The return type is different from the returned value. std::forward returns a value here . This value is not stored in a variable, but returned directly. Therefore, the returned value is an rvalue , and the above instance cannot be called. The first print() function, although the type of the returned value is an rvalue reference int && )

// 对于下面的代码, auto是被推断为 int&& 的,可以调用上面实例中的第一个print函数
// 因为这时用一个变量a来接收返回的右值,a就变成了左值,左值可以调用print()函数
auto a = std::forward<int>(v);
#include <iostream>

template<typename T>
void print(T & t){ // 只接受左值
    std::cout << "Lvalue ref" << std::endl;
}

int main(int argc, char * argv[]){

    int v = 1;
    auto a = std::forward<int>(v); // int &&a = 1; 
    // a 变成了左值
    print(a);
}

2-2--std::forward vernacular summary

        When an actual parameter is passed into std::forward, and its explicit type is T , the returned value is the same as the actual parameter , and the return type is T&& ;

3--The use of std::move

3-1--The source code of std::move

/**
   *  @brief  Convert a value to an rvalue.
   *  @param  __t  A thing of arbitrary type.
   *  @return The parameter cast to an rvalue-reference to allow moving it.
  */
  template<typename _Tp>
    _GLIBCXX_NODISCARD
    constexpr typename std::remove_reference<_Tp>::type&&
    move(_Tp&& __t) noexcept
    { return static_cast<typename std::remove_reference<_Tp>::type&&>(__t); }

Simply combine the source code of the template to analyze the above code example:

        ① For std::move(v), v is an lvalue , and the type of v is int & . When passed to the std::move(v) function, _Tp is inferred to be int & , and the source code is equivalent to int&& move( int& _t); Receive an lvalue, the return type is int&&, that is, the rvalue reference type, and the returned value is an rvalue , so you can only call the second print() function in the above code example;

        ② For std::move(v), v is an lvalue , and the type of v is int . When passed to the std::move(v) function, _Tp is inferred to be int , and the source code is equivalent to int&& move(int&& _t ); Receive an rvalue, the return type is int&&, that is, the rvalue reference type, and the returned value is an rvalue , so you can only call the second print() function in the above code example;

3-2--std::move vernacular summary

std::move() returns an rvalue         regardless of whether an lvalue or an rvalue is passed in, and its type is an rvalue reference type ;

Guess you like

Origin blog.csdn.net/weixin_43863869/article/details/132433758
Recommended