经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » C++ » 查看文章
详解C++右值引用
来源:jb51  时间:2021/6/7 10:29:22  对本文有异议

概述

在C++中,常量、变量或表达式一定是左值(lvalue)或右值(rvalue)。

左值:非临时的(具名的,可在多条语句中使用,可以被取地址)。可以出现在等号的左边或右边。可分为非常量左值和常量左值。

右值:临时的(不具名的,只在当前语句中有效,不能取地址)。只能出现在等号的右边。可分为非常量右值和常量右值。

左值引用:对左值的引用就是左值引用。可分为非常量左值引用和常量左值引用。

注:常量左值引用是“万能”的引用类型,可以绑定到所有类型的值,包括非常量左值、常量左值、非常量右值和常量右值。

右值引用(Rvalue References):对右值的引用就是右值引用。可分为非常量右值引用和常量右值引用。

为临时对象的右值,它的生命周期很短暂,一般在执行完当前这条表达式之后,就释放了。

通过将其赋值给右值引用,可以在不进行昂贵的拷贝操作的情况下被“续命”,让其生命周期与右值引用类型变量的生命周期一样长。

右值引用的两个基本特性:移动语义(Move Semantics)和完美转发(Perfect Forwarding)

移动语义(Move Semantics)

可将资源从一个对象转移到另一个对象;主要解决减少不必要的临时对象的创建、拷贝与销毁。

移动构造函数MyClass(Type&& a):当构造函数参数是一个右值时,优先使用移动构造函数而不是拷贝构造函数MyClass(const Type& a)。

移动赋值运算符Type& operator = (Type&& a):当赋值的是一个右值时,优先使用移动赋值而不是拷贝赋值运算符Type& operator = (const Type& a)。

  1. #include <iostream>
  2. #include <string>
  3. #include <utility>
  4.  
  5. struct MyClass
  6. {
  7. std::string s;
  8. MyClass(const char* sz) : s(sz)
  9. {
  10. std::cout << "MyClass sz:" << sz << std::endl;
  11. }
  12. MyClass(const MyClass& o) : s(o.s)
  13. {
  14. std::cout << "copy construct!\n";
  15. }
  16.  
  17. MyClass(MyClass&& o) noexcept : s(std::move(o.s))
  18. {
  19. std::cout << "move construct!\n";
  20. }
  21.  
  22. MyClass& operator=(const MyClass& other) { // copy assign
  23. std::cout << "copy assign!\n";
  24. s = other.s;
  25. return *this;
  26. }
  27. MyClass& operator=(MyClass&& other) noexcept { // move assign
  28. std::cout << "move assign!\n";
  29. s = std::move(other.s);
  30. return *this;
  31. }
  32.  
  33. static MyClass GetMyClassGo(const char* sz)
  34. {
  35. MyClass o(sz); // 注意:可能会被NRVO优化掉
  36. return o;
  37. }
  38. };
  39.  
  40. void func0(MyClass o)
  41. {
  42. std::cout << o.s.c_str() << std::endl;
  43. }
  44.  
  45. void func1(MyClass& o)
  46. {
  47. std::cout << o.s.c_str() << std::endl;
  48. }
  49.  
  50. void func2(const MyClass& o)
  51. {
  52. std::cout << o.s.c_str() << std::endl;
  53. }
  54.  
  55. void func3(MyClass&& o)
  56. {
  57. std::cout << o.s.c_str() << std::endl;
  58. }
  59.  
  60. int main(int arg, char* argv[])
  61. {
  62. MyClass a1("how");
  63. MyClass a2("are");
  64.  
  65. a2 = a1; // copy assign 注:a1是一个左值
  66.  
  67. a2 = MyClass("you"); // move assign 注:MyClass("you")是一个右值
  68.  
  69. MyClass a3(a1); // copy construct 注:a1是一个左值
  70. MyClass&& a4 = MyClass::GetMyClassGo("go"); // move construct 注:发生在MyClass::GetMyClassGo()内部
  71. MyClass a5 = MyClass::GetMyClassGo("china"); // move construct两次 注:一次发生在MyClass::GetMyClassGo()内部;另一次发生在将返回值赋值给a5
  72. MyClass a6("let");
  73. MyClass a7("it");
  74. MyClass a8("go");
  75. MyClass a9("!");
  76.  
  77. func0(a6); // copy construct
  78. func1(a7);
  79. func2(a8);
  80. //func3(a9); // 编译error: 不能把一个左值赋值给右值
  81.  
  82. func0(MyClass::GetMyClassGo("god")); // move construct两次 注:一次发生在MyClass::GetMyClassGo()内部;另一次发生在将返回值赋值给foo0参数时
  83. //func1(MyClass::GetMyClassGo("is")); // 编译error: 不能把一个右值赋值给左值
  84. func2(MyClass::GetMyClassGo("girl")); // move construct 注:发生在MyClass::GetMyClassGo()内部
  85. func3(MyClass::GetMyClassGo("!")); // move construct 注:发生在MyClass::GetMyClassGo()内部
  86.  
  87. return 0;
  88. }

注:测试以上代码一定要关闭C++编译器优化技术 -- RVO、NRVO和复制省略

使用std::move来实现移动语义

将一个左值或右值强制转化为右值引用。 注:UE4中对应为MoveTemp模板函数

std::move(en chs)并不会移动任何东西,只是将对象的状态或者所有权从一个对象转移到另一个对象。注:只是转移,没有内存的搬迁或者内存拷贝。

① 基本类型(如:int、double等)被std::move移动后,其数值不会发生变化

② 复合类型被std::move移动后,处于一个未定义,但有效的状态(大部分成员函数仍有意义)例如:标准库中的容器类对象被移动后,会变成空容器

完美转发(Perfect Forwarding)

针对模板函数,使用全能引用将一组参数原封不动的传递给另一个函数。

原封不动指:左值、右值、是否为const均不变。带来如下3方面好处:

① 保证左值、右值的属性

② 避免不必要的拷贝操作

③避免模版函数需要为左值、右值、是否为const的参数来实现不同的重载

全能引用(universal references、转发引用)是一种特殊的模板引用类型,采用右值引用的语法形式(但它并不是右值引用)。如:template <class T> void func(T&& t) {}

T&& t在发生自动类型推断的时候,它是未定的引用类型(universal references),T取决于传入的参数t是右值还是左值。右值经过T&&变为右值引用,而左值经过T&&变为左值引用。

std::move就是使用全能引用实现的。其定义如下:

  1. template <typename T>
  2. typename remove_reference<T>::type&& move(T&& t)
  3. {
  4. return static_cast<typename remove_reference<T>::type &&>(t);
  5. }
  6.  
  7.  
  8. /*****************************************
  9. std::remove_reference功能为去除类型中的引用
  10.  
  11. std::remove_reference<T &>::type ---> T
  12. std::remove_reference<T &&>::type ---> T
  13. std::remove_reference<T>::type ---> T
  14. ******************************************/
  15. //原始的,最通用的版本
  16. template <typename T> struct remove_reference{
  17. typedef T type; //定义T的类型别名为type
  18. };
  19. //部分版本特例化,将用于左值引用和右值引用
  20. template <class T> struct remove_reference<T&> //左值引用
  21. { typedef T type; }
  22. template <class T> struct remove_reference<T&&> //右值引用
  23. { typedef T type; }

① 当t为左值时,展开为:U&& move(U& t) 注:右值引用类型变量也是左值

② 当t为右值时,展开为:U&& move(U&& t)

最后,通过static_cast<>进行强制类型转换返回右值引用。注:static_cast之所以能使用类型转换,是通过remove_refrence::type模板移除T&&,T&的引用,获取具体类型T(模板偏特化)。

引用折叠

规律:含左值引用就是左值引用,否则就是右值引用

使用std::forward实现参数的完美转发。其定义如下(en chs):

  1. template <typename T>
  2. T&& forward(remove_reference_t<T>& arg) // forward an lvalue as either an lvalue or an rvalue
  3. {
  4. return static_cast<T&&>(arg);
  5. }
  6.  
  7. template <typename T>
  8. T&& forward(remove_reference_t<T>&& arg) // forward an rvalue as an rvalue
  9. {
  10. static_assert(!is_lvalue_reference_v<T>, "bad forward call");
  11. return static_cast<T&&>(arg);
  12. }

最后,通过static_cast<>进行引用折叠,并强制类型转换后,实现原封不动转发参数。 注:UE4中对应为Forward模板函数

  1. void bar(int& a, int&& b)
  2. {
  3. int c = a + b;
  4. }
  5.  
  6. void func(int a, int&& b)
  7. {
  8. int c = a + b;
  9. }
  10.  
  11. template <typename A, typename B>
  12. void foo(A&& a, B&& b) { // a, b为左值引用或右值引用
  13. bar(std::forward<A>(a), std::forward<B>(b)); // 在std::forward转发前后,参数a,b的类型完全不变
  14. }
  15.  
  16. int main(int arg, char* argv[])
  17. {
  18. int a = 10;
  19.  
  20. foo(a, 20); // 展开为void foo(int& a, int&& b),经过std::forward完美转发后,会调用到void bar(int& a, int&& b)函数
  21.  
  22. func(std::forward<int>(a), std::forward<int&&>(30)); // 经过std::forward完美转发后,会调用到void func(int a, int&& b)函数
  23.  
  24. return 0;
  25. }

以上就是详解C++右值引用的详细内容,更多关于C++右值引用的资料请关注w3xue其它相关文章!

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号