[转] 理解std::move和std::forward

本文转自:https://blog.csdn.net/f110300641/article/details/83477160

std::move

    c++11中提供了std::move()来将左值转换为右值引用,从而方便的使用移动语义。move是将对象的状态或者所有权从一个对象转移到另一个对象,只是转移,没有内存拷贝。 
    c++中所有容器都实现了move语义,方便我们实现性能优化。move对于拥有形如对内存、文件句柄等资源的成员的对象有效。如果是一些基本类型,比如int或char[10]数组等,如果使用move,仍然会发生拷贝(因为没有对应的移动构造函数)。

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

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

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

std::forward

    右值引用类型是独立于值的,一个右值引用参数作为函数的形参,在函数内部再转发该参数的时候它已经变成一个左值,并不是他原来的类型。

需要一种方法能够按照参数原来的类型转发到另一个函数,这种转发类型称为完美转发

完美转发(Perfect Forwarding),是指在函数模板中,完全依照模板的参数的类型(即保持参数的左值、右值特征),将参数传递给函数模板中调用的另外一个函数。C++11中提供了这样的一个函数std::forward,它是为转发而生的,不管参数是T&&这种未定的引用还是明确的左值引用或者右值引用,它会按照参数本来的类型转发。

 
  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没有move任何东西,std::forward没有转发任何东西。在运行期,它们没有做任何事情。它们没有产生需要执行的代码,一byte都没有。

std::move和std::forward只不过就是执行cast的两个函数(实际上是函数模板)。std::move无条件地把它的参数转换成一个右值,而std::forward只在特定条件满足的情况下执行这个转换。

这里给出C++11中std::move实现的一个例子。它没有完全遵循标准的细节,但是很接近了。

 
  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. }

std::move所做的所有事情就是转换它的参数为一个右值。它做的是转换,没有做move。

std::forward的情况和std::move相类似,但是std::forward是一个有条件的转换。为了理解它什么时候转换,什么时候不转换,回忆一下std::forward是怎么使用的。最常见的情况就是,一个带universal引用的参数被传给另外一个参数:

 
  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. }

考虑一下两个logAndProcess调用,一个使用左值,另外一个使用右值:

 
  1. Widget w;

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

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

当我们用左值调用logAndProcess的时候,我们自然是希望这个左值作为一个左值被转发给process,然后当我们使用右值调用logAndProcess时,我们希望右值版本的process被调用。

但是param就和所有的函数参数一样,是一个左值。因此在logAndProcess内部总是调用左值版本的process。为了防止这样的事情发生,我们需要一种机制来让param在它被一个右值初始化(传给logAndProcess的参数)的时候转换成右值。这正好就是std::forward做的事情。这也就是为什么std::forward是一个条件转换:它只把用右值初始化的参数转换成右值。

我们是不是可以去掉std::move并且在所有的地方都只使用std::forward。从技术的角度来看,回答是可以:std::forward能做到所有的事情。std::move不是必须的。当然,这两个函数函数都不是“必须的”,因为我们能在使用的地方写cast,但是我希望我们能同意它们是必须的函数。

std::move的优点是方便,减少相似的错误,并且更加清晰。考虑一个类,对于这个类我们想要记录它的move构造函数被调用了多少次。

 
  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. };

为了用std::forward来实现相同的行为,代码看起来像是这样的:

 
  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. };

首先注意std::move只需要一个函数参数(rhs.s),而std::forward却需要一个函数参数(rhs.s)以及一个模板类型参数(std::string)。然后注意一下我们传给std::forward的类型应该是一个非引用类型,因为我们约定好传入右值的时候要这么编码(传入一个非引用类型,看Item 28)。也就是说,这意味着std::move需要输入的东西比std::forward更少,还有,它去掉了我们传入的参数是右值时的麻烦(记住类型参数的编码)。它也消除了我们传入错误类型(比如,std::string&,这会导致数据成员用拷贝构造函数来替换move构造函数)的可能。

更加重要的是,使用std::move表示无条件转换到一个右值,然后使用std::forward表示只有引用的是右值时才转换到右值。这是两种非常不同的行为。第一个常常执行move操作,但是第二个只是传递(转发)一个对象给另外一个函数并且保留它原始的左值属性或右值属性。因为这些行为如此地不同,所以我们使用两个函数(以及函数名)来区分它们是很好的主意。

            你要记住的事

  • std::move无条件转换到右值。就其本身而言,它没有move任何东西。
  • std::forward只有在它的参数绑定到一个右值上的时候,它才转换它的参数到一个右值。
  • std::move和std::forward在运行期都没有做任何事情。

猜你喜欢

转载自blog.csdn.net/gouguofei/article/details/103666358
今日推荐