聊聊 C++ 右值引用 和 移动构造函数

打印 上一主题 下一主题

主题 896|帖子 896|积分 2688

一: 背景

最近在看 C++ 的右值引用和移动构造函数,感觉这东西一时半会还挺难理解的,可能是没踩过这方面的坑,所以没有那么大的深有体会,不管怎么说,这一篇我试着聊一下。
二: 右值引用

1. 它到底解决了什么问题?

在其他编程语言中,很少听到 右值引用 这个词,我个人感觉还是 C++ 这个 值类型 优先的语言基因决定的,我们都知道 值类型 作为方法参数或者返回值时会生成自身的副本,如果 值类型 很大,那一来一回生成若干个深复制的 临时对象 将会产生巨大的性能开销。
总结一句话:右值引用 就是尽可能的减少这中间 临时对象 个数,尤其是关联到 heap 上的对象,仅此而已。
2. 右值引用是个什么样子?

说到 右值引用 得先说什么是 右值,左值 , 左值 一般都是带有内存地址的变量,而 右值 一般是立即数或者运算过程中的临时对象,这种对象不会有地址值,是不是很绕,我举个例子吧。
  1. int main()
  2. {
  3.         int i = 10;
  4.         int j = 11;
  5.         int sum = i + j;
  6. }
复制代码

  • 10,11,(i+j)
属于右值,因为它本身没有内存地址,除非把它们放入到栈中或者堆中。

  • i,j,sum
属于左值,因为它们是线程栈上地址的标识符。
知道了 左右值 概念,接下来理解 左右值引用 就很简单了,既然是 引用,必然是多个变量指向同一个地址,对吧,修改下代码如下:
  1. int main()
  2. {
  3.         int i = 10;
  4.         int& k = i;                //左值引用
  5.         int&& m = 10;        //右值引用
  6. }
复制代码
接下来看下汇编代码:
  1.     33:         int i = 10;
  2. 00FB182F  mov         dword ptr [ebp-0Ch],0Ah  
  3.     34:         int& k = i;       
  4. 00FB182F  mov         dword ptr [ebp-0Ch],0Ah  
  5. 00FB1836  lea         eax,[ebp-0Ch]  
  6. 00FB1839  mov         dword ptr [ebp-18h],eax  
  7.     36:         int&& m = 10;       
  8. 00FB183C  mov         dword ptr [ebp-30h],0Ah  
  9. 00FB1843  lea         eax,[ebp-30h]  
  10. 00FB1846  mov         dword ptr [ebp-24h],eax
复制代码
从汇编代码看,它们是一模一样的,也就是说在汇编层面,其实并没有 右值引用 和 左值引用 一说。
有了这些基础,我们来看下更复杂的 class 结构。
三: 右值引用如何减少对象的创建

1. 简要思路

其实仔细想一想,减少临时对象的创建,无非就是在运算过程中复用一些对象,不需要每次都走赋值构造函数来进行深复制,画个图就像下面这样。

明白了这个思路,接下来我们举一个例子说明。
2. 一个简单的例子

C++ 最烦的地方就是有太多的构造函数, 数不胜数,太尴尬了,这里我做一个简单的 + 操作例子。
  1. #include <iostream>
  2. #include <vector>
  3. using namespace std;
  4. class StringBuidler {
  5. public:
  6.         char* str;
  7.         int length;
  8. public:
  9.         StringBuidler() {}
  10.         StringBuidler(int len, char c) {
  11.                 this->str = new char[len];
  12.                 this->str[0] = c;
  13.                 this->length = len;
  14.         }
  15.         StringBuidler(const StringBuidler& s) {
  16.                 printf("StringBuidler:深复制 \n");
  17.                 this->length = s.length;
  18.                 this->str = new char[s.length];
  19.                 for (size_t i = 0; i < length; i++)
  20.                 {
  21.                         this->str[i] = s.str[i];
  22.                 }
  23.         }
  24.         StringBuidler operator+(const StringBuidler& p) {
  25.                 StringBuidler tmp;
  26.                 tmp.length = this->length + p.length;
  27.                 tmp.str = new char[tmp.length];
  28.                 int index = 0;
  29.                 for (size_t i = 0; i < this->length; i++)
  30.                 {
  31.                         tmp.str[index++] = this->str[i];
  32.                 }
  33.                 for (size_t i = 0; i < p.length; i++)
  34.                 {
  35.                         tmp.str[index++] = p.str[i];
  36.                 }
  37.                 return tmp;
  38.         }
  39. };
  40. int main()
  41. {
  42.         StringBuidler s1(10, 'a');
  43.         StringBuidler s2(5, 'b');
  44.         StringBuidler s3 = s1 + s2;
  45.         printf("s3.length=%d, s1.length=%d, s2.length=%d \n", s3.length, s1.length, s2.length);
  46. }
复制代码

从这个例子中可以看到,s1+s2 操作中出现了一次 深copy,具体代码出现在 return 处,汇编代码如下:

因为是深复制,所以会再次生成一个 new char[] ,如果 new char[] 很大,那将会是不必要的性能开销,能不能像我画的图一样,将 s3 中的 str 指针直接指向 tmp 所持有的 heap 上的 char[] 数组来达到复用目的呢? 肯定是可以的。
3. 性能优化方案

这里需要用 右值引用 + 移动构造函数 让 s3.str 指向 tmp.str,从而避免复制构造函数,在 StringBuilder 类中加一个方法如下:
  1.         StringBuidler(StringBuidler&& s) {
  2.                 this->str = s.str;
  3.                 this->length = s.length;
  4.                 s.str = nullptr;
  5.         }
复制代码
然后把程序跑起来,截图如下:

可以看到,深复制已经没有了,这个过程会在  return 处被调用,编译器会判断如果是右值的话,自动走 移动构造函数,没有这个函数就会走 赋值构造函数。
四: 总结

总之 右值引用 可以让你尽可能的复用一些中间对象,达到一个性能上的提升,其实对 C# 程序员来说,这么简单的引用赋值,C++ 搞出了这么多概念,真的很难理解,可能还是那句话,这是 C++ 的值类型优先的基因决定的。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

石小疯

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表