经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » C++ » 查看文章
C++模板编程特性之移动语义
来源:jb51  时间:2022/8/23 16:19:16  对本文有异议

C++的值类型

我们知道,每个变量都有类型,或整形或字符型等来进行了分类,不仅如此,C++表达式(带有操作数的操作符、字面量、变量名等)在类型的属性上,还有一种属性,即值类别(value category)。且每个表达式只属于三种基本值尖别中的一种:左值(lvalue),右值(rvalue),将亡值(xvalue),每个值类别都与某种引用类型对应。

其中,左值和将亡值成为泛左值(generalized value,gvalue),纯右值和将亡值合称为右值(right value,rvalue)。

一般我们讲,左值就是可以取地址的,具有名字的,比如 int a; a是变量的名字,&a是变量的地址,a就是左值。那么右值呢,自然就是不可以取地址的,比如int b=10; 而这个10就是一个右值,在内存中不会分配有地址,自然也不能取地址。

将亡值,则是指在调用某个函数退出返回时,如果函数有返回值,那么就会有将亡值的存在,为什么称之为将亡值,就是说这个值在函数作用域创建,但由于函数返回结束,局部变量都会销毁,故会产生一个将亡值来接收这个值,完成赋值的任务。

从上图也可以看出,将亡值既可能转为左值,也可能成为右值,那么关键就在于要看是否具有名字了。

下面看这样一段程序:

  1. #include<iostream>
  2. #include<type_traits>
  3. using namespace std;
  4. class MyString
  5. {
  6. private:
  7. char* str; // heap;
  8. public:
  9. MyString(const char* p = nullptr) :str(nullptr)
  10. {
  11. if (p != nullptr)
  12. {
  13. int n = strlen(p) + 1;
  14. str = new char[n];
  15. strcpy_s(str, n, p);
  16. }
  17. cout << "Create MyString: " << this << endl;
  18. }
  19. MyString(const MyString& st)
  20. {
  21. if(st.str!=NULL)
  22. str = st.str;
  23. cout << "Copy Create MyString: " << this << endl;
  24. }
  25. MyString& operator=(const MyString& st)
  26. {
  27. if (st.str != NULL)
  28. str = st.str;
  29. cout << this << " operator=(const MyString &): " << &st << endl;
  30. return *this;
  31. }
  32. ~MyString()
  33. {
  34. delete[]str;
  35. str = nullptr;
  36. cout << "Destroy MyString : " << this << endl;
  37. }
  38. void PrintString() const
  39. {
  40. if (str != nullptr)
  41. {
  42. cout << str << endl;
  43. }
  44. }
  45. };
  46. int main()
  47. {
  48. MyString *a=new MyString("lisa");
  49. MyString *b = a;
  50. delete b;
  51. a->PrintString();
  52. return 0;
  53. }

MyString类型成员有指针变量,且采用浅拷贝方式。当程序运行时,可以看到,两个指针指向了同一个地址,此时,若释放了b指针,再以a指针访问指针成员,就会出现问题。

还有,当函数以值类型返回,构造临时对象,若有指针变量,且采用浅拷贝,就会出现多次析构的问题,导致程序崩溃。

当我们将程序都改为深拷贝时,深拷贝又会导致,程序多次骚扰对空间,此时就提出了move语义。

std::move

std::move其实并没有移动任何东西,它唯一的功能是将一个左值强制转化为右值引用,继而可以通过右值引用使用该值,以用于移动语义。从实现上讲,move基本等同于一个类型转换。

值得注意的是,通过move转化成右值后,被转化的左值的生命周期并没有随着左右值的转化而改变。但通常情况下,我们需要转换成右值引用的还是一个确定生命期即将结束的对象。

右值引用与移动构造和移动赋值

在c++11中增加了右值引用的概念,即对右值的引用,通过右值引用,可以延长右值的生命期。我们都知道左值引用是变量值的别名,那么右值引用则是不具名变量的别名。

右值引用是不能绑定到任何左值的,但有个例外,常量左值是一个万能引用,可以引用任何值,包括右值引用。

  1. class MyString
  2. {
  3. private:
  4. char* str; // heap;
  5. public:
  6. MyString(const char* p = nullptr) :str(nullptr)
  7. {
  8. if (p != nullptr)
  9. {
  10. int n = strlen(p) + 1;
  11. str = new char[n];
  12. strcpy_s(str, n, p);
  13. }
  14. cout << "Create MyString: " << this << endl;
  15. }
  16. MyString(const MyString& st)
  17. {
  18. if (st.str != nullptr)
  19. {
  20. int n = strlen(st.str) + 1;
  21. str = new char[n];
  22. strcpy_s(str, n, st.str);
  23. }
  24. cout << "Copy Create MyString: " << this << endl;
  25. }
  26. MyString& operator=(const MyString& st)
  27. {
  28. if (this != &st && str != st.str)
  29. {
  30. delete[]str;
  31. if (st.str != nullptr)
  32. {
  33. int n = strlen(st.str) + 1;
  34. str = new char[n];
  35. strcpy_s(str, n, st.str);
  36. }
  37. }
  38. cout << this << " operator=(const MyString &): " << &st << endl;
  39. return *this;
  40. }
  41. MyString(MyString&& st)
  42. {
  43. str = st.str;
  44. st.str = nullptr;
  45. cout << "Move Copy Create MyString" << this << endl;
  46. }
  47. MyString& operator=(MyString&& st)
  48. {
  49. if (this == &st) return *this;
  50. if (this->str == st.str)
  51. {
  52. st.str = nullptr;
  53. return *this;
  54. }
  55. delete[]str;
  56. str = st.str;
  57. st.str = nullptr;
  58. cout << "Move operator=(MyString &&)" << endl;
  59. return *this;
  60. }
  61. ~MyString()
  62. {
  63. delete[]str;
  64. str = nullptr;
  65. cout << "Destroy MyString : " << this << endl;
  66. }
  67. void PrintString() const
  68. {
  69. if (str != nullptr)
  70. {
  71. cout << str << endl;
  72. }
  73. }
  74. };
  75. int main()
  76. {
  77. const MyString stra("hello");
  78. MyString strb;
  79. strb = std::move(stra);//调用普通的赋值方法
  80. strb.PrintString();
  81. return 0;
  82. }

这里的move还是调用普通的赋值函数,并未做到真正的资源转移,但是若写成如下结构:

  1. int main()
  2. {
  3. const MyString stra("hello");
  4. MyString strb;
  5. //strb = std::move(stra);//调用普通的赋值方法
  6. strb = (MyString&&)stra;
  7. strb.PrintString();
  8. return 0;
  9. }

通过右值引用,可以延长右值的生命期。从而,有了右值引用出现,这个时候配合移动构造与移动赋值,就可以完成资源转移了。

然后,我们再看一个例子:

  1. MyString& fun()
  2. {
  3. MyString st=("newdata");
  4. return st;//xvalue
  5. }
  6. int main()
  7. {
  8. MyString("zhangsan").PrintString();
  9. const MyString& a = fun();
  10. a.PrintString();
  11. MyString& b = fun();
  12. b.PrintString();
  13. return 0;
  14. }

在程序运行时,会发现程序崩溃了,原因是:

函数中返回局部对象的引用,因为函数调用结束会销毁局部对象,而引用则就成为了非法的访问。因为不要在函数中返回局部对象的引用。

若我们将fun()函数的返回改为右值引用呢?

  1. MyString&& fun()
  2. {
  3. return MyString("newdata");
  4. }
  5. int main()
  6. {
  7. MyString("zhangsan").PrintString();
  8. const MyString& a = fun();//x
  9. a.PrintString();
  10. //MyString& b = fun();
  11. //b.PrintString();
  12. MyString&& c = fun();//x
  13. c.PrintString();
  14. MyString&& d = c;//error
  15. return 0;
  16. }

将亡值回去的时候,就得看看有没有具名,一旦具名就是左值了,否则是右值

可以发现,右值引用是不具名的,但是右值引用本身却是个左值,经过右值引用b接收后,就已经变成了左值,具有了名字。

到此这篇关于C++模板编程特性之移动语义的文章就介绍到这了,更多相关C++移动语义内容请搜索w3xue以前的文章或继续浏览下面的相关文章希望大家以后多多支持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号