[Turn] Understanding std::move and std::forward

This article is transferred from: https://blog.csdn.net/f110300641/article/details/83477160

 

std::move

    Std::move() is provided in c++11 to convert lvalues ​​into rvalue references, so as to facilitate the use of move semantics. A move is to transfer the state or ownership of an object from one object to another, just a transfer without a memory copy. 
    All containers in c++ implement move semantics, which is convenient for us to achieve performance optimization. Move is effective for objects that have members that have resources such as memory and file handles. If it is some basic type, such as int or char[10] array, etc., if you use move, copying will still occur (because there is no corresponding move constructor).

 
  1. std::list<std::string> tokens;

  2. //发生了移动构造。list的实现,将目的资源句柄赋值为源资源句柄,而将源资源句柄清空

  3. std::list<std::string> t = std::move(tokens);

std::forward

    The rvalue reference type is independent of the value. An rvalue reference parameter is used as a formal parameter of the function. When the parameter is forwarded inside the function, it has become an lvalue, not its original type.

A method is needed to forward to another function according to the original type of the parameter. This type of forwarding is called perfect forwarding .

Perfect Forwarding refers to that in the function template, the parameters are passed to another function called in the function template completely according to the type of the parameters of the template (that is, the lvalue and rvalue characteristics of the parameters are maintained). C++11 provides such a function std::forward, which is born for forwarding, regardless of whether the parameter is an undefined reference such as T&& or an explicit lvalue reference or rvalue reference, it will follow the original parameter Type forwarding.

 
  1. #include<iostream>

  2. using namespace std;

  3. template<typename T>

  4. void print(T& t)

  5. {

  6. cout << "lvalue" << endl;

  7. }

  8.  
  9. template<typename T>

  10. void print(T&& t)

  11. {

  12. cout << "rvalue" << endl;

  13. }

  14.  
  15. template<typename T>

  16. void TestForward(T && v)

  17. {

  18. //print(v); //编译错误 不知道是哪个print

  19. print(std::forward<T>(v));

  20. print(std::move(v));

  21. }

  22.  
  23. int main()

  24. {

  25. TestForward(1);

  26. int x = 1;

  27. //TestForward(x); //使print(std::forward<T>(v));编译错误

  28. TestForward(std::forward<int>(x));

  29. return 0;

  30. }

  31. //rvalue

  32. //rvalue

  33. //rvalue

  34. //rvalue

std::move does not move anything, std::forward does not forward anything. During runtime, they do not do anything. They did not generate code to be executed, not a byte.

std::move and std::forward are just two functions (actually function templates) that perform cast. std::move unconditionally converts its parameters into an rvalue, while std::forward performs this conversion only when certain conditions are met.

Here is an example of std::move implementation in C++11. It does not fully follow the standard details, but it is close.

 
  1. template<typename T> //在命名空间std中

  2. typename remove_reference<T>::type&& move(T&& param)

  3. {

  4. using ReturnType = typename remove_reference<T>::type&&; //别名声明

  5.  
  6. return static_cast<ReturnType>(param);

  7. }

All that std::move does is convert its argument to an rvalue. What it does is conversion, not move.

The situation of std::forward is similar to std::move, but std::forward is a conditional transition. In order to understand when it is converted and when not, recall how std::forward is used. The most common situation is that a parameter with a universal reference is passed to another parameter:

 
  1. void process(const Widget& lvalArg); // 参数为左值

  2. void process(Widget&& rvalArg); // 参数为右值

  3.  
  4. template<typename T> // 把参数传给process

  5. void logAndProcess(T&& param) // 的模板

  6. {

  7. process(std::forward<T>(param));

  8. }

Consider two logAndProcess calls, one using an lvalue and the other using an rvalue:

 
  1. Widget w;

  2.  
  3. logAndProcess(w); // 用左值调用

  4. logAndProcess(std::move(w)); // 用右值调用

When we call logAndProcess with an lvalue, we naturally want this lvalue to be forwarded to the process as an lvalue, and then when we call logAndProcess with an rvalue, we want the rvalue version of the process to be called.

But param, like all function parameters, is an lvalue. Therefore, the lvalue version of process is always called inside logAndProcess. To prevent this from happening, we need a mechanism to convert param to an rvalue when it is initialized by an rvalue (parameter passed to logAndProcess). This is exactly what std::forward does. This is why std::forward is a conditional conversion: it only converts parameters initialized with rvalues ​​into rvalues.

Can we remove std::move and use only std::forward everywhere? From a technical point of view, the answer is yes: std::forward can do everything. std::move is not required. Of course, these two functions are not "required" because we can write cast where they are used, but I hope we can agree that they are required functions.

The advantages of std::move are convenience, fewer similar errors, and more clarity. Consider a class, for which we want to record how many times its move constructor is called.

 
  1. class Widget {

  2. public:

  3. Widget(Widget&& rhs)

  4. : s(std::move(rhs.s))

  5. { ++moveCtorCalls;}

  6. }

  7.  
  8. ...

  9.  
  10. private:

  11.  
  12. static std::size_t moveCtorCalls;

  13. std::string s;

  14. };

In order to achieve the same behavior with std::forward, the code looks like this:

 
  1. class Widget {

  2. public:

  3. Widget(Wdiget&& rhs) //不常见,以及不受欢迎的实现

  4. : s(std::forward<std::string>(rhs.s))

  5. //译注:为什么是std::string请看Item 1,用右值传入std::string&& str的话

  6. //推导的结果T就是std::string,用左值传入,则推导的结果T会是std::string&

  7. //然后这个T就需要拿来用作forward的模板类型参数了。

  8. //详细的解释可以参考Item28

  9. { ++moveCtorCalls; }

  10. };

First, note that std::move only requires one function parameter (rhs.s), while std::forward requires one function parameter (rhs.s) and a template type parameter (std::string). Then note that the type we passed to std::forward should be a non-reference type, because we agreed to encode this when passing in rvalues ​​(passing in a non-reference type, see Item 28). In other words, this means that std::move requires less input than std::forward, and it removes the trouble when the parameters we pass in are rvalues ​​(remember the encoding of type parameters). It also eliminates the possibility of us passing in the wrong type (for example, std::string&, which will cause the data member to use the copy constructor to replace the move constructor).

More importantly, use std::move to indicate an unconditional conversion to an rvalue, and then use std::forward to indicate that it is converted to an rvalue only when the reference is an rvalue. These are two very different behaviors. The first one often performs the move operation, but the second one just passes (forwards) an object to another function and retains its original lvalue attribute or rvalue attribute. Because these behaviors are so different, it is a good idea for us to use two functions (and function names) to distinguish them.

            Things you have to remember

  • std::move unconditionally converts to an rvalue. On its own, it doesn't move anything.
  • std::forward only converts its parameters to an rvalue when its parameter is bound to an rvalue.
  • Neither std::move nor std::forward does anything at runtime.

Guess you like

Origin blog.csdn.net/gouguofei/article/details/103666358