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

事件营销求职招聘QQ群 http://www.guanxxg.com/news/roll/1561241.html
一:背景

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

二:右值引用1.它到底解决了什么问题?

在其他编程语言中,很少听到右值引用这个词,我个人感觉还是C++这个值类型优先的语言基因决定的,我们都知道值类型作为方法参数或者返回值时会生成自身的副本,如果值类型很大,那一来一回生成若干个深复制的临时对象将会产生巨大的性能开销。

总结一句话:右值引用就是尽可能的减少这中间临时对象个数,尤其是关联到hap上的对象,仅此而已。

2.右值引用是个什么样子?

说到右值引用得先说什么是右值,左值,左值一般都是带有内存地址的变量,而右值一般是立即数或者运算过程中的临时对象,这种对象不会有地址值,是不是很绕,我举个例子吧。

intmain(){  inti=10;  intj=11;  intsum=i+j;}

10,11,(i+j)

属于右值,因为它本身没有内存地址,除非把它们放入到栈中或者堆中。

i,j,sum

属于左值,因为它们是线程栈上地址的标识符。

知道了左右值概念,接下来理解左右值引用就很简单了,既然是引用,必然是多个变量指向同一个地址,对吧,修改下代码如下:

intmain(){  inti=10;  intk=i;    //左值引用  intm=10;  //右值引用}

接下来看下汇编代码:

33:  inti=10;00FBFmovdwordptr[bp-0Ch],0Ah34:  intk=i;  00FBFmovdwordptr[bp-0Ch],0Ah00FBlaax,[bp-0Ch]00FBmovdwordptr[bp-18h],ax36:  intm=10;  00FBCmovdwordptr[bp-30h],0Ah00FBlaax,[bp-30h]00FBmovdwordptr[bp-24h],ax

从汇编代码看,它们是一模一样的,也就是说在汇编层面,其实并没有右值引用和左值引用一说。

有了这些基础,我们来看下更复杂的class结构。

三:右值引用如何减少对象的创建1.简要思路

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

明白了这个思路,接下来我们举一个例子说明。

2.一个简单的例子

C++最烦的地方就是有太多的构造函数,数不胜数,太尴尬了,这里我做一个简单的+操作例子。

#includiostam#includvctorusingnamspacstd;classStringBuidlr{public:  char*str;  intlngth;public:  StringBuidlr(){}  StringBuidlr(intln,charc){    this-str=nwchar[ln];    this-str[0]=c;    this-lngth=ln;  }  StringBuidlr(constStringBuidlrs){    printf("StringBuidlr:深复制\n");    this-lngth=s.lngth;    this-str=nwchar[s.lngth];    for(siz_ti=0;ilngth;i++)    {      this-str[i]=s.str[i];    }  }  StringBuidlroprator+(constStringBuidlrp){    StringBuidlrtmp;    tmp.lngth=this-lngth+p.lngth;    tmp.str=nwchar[tmp.lngth];    intindx=0;    for(siz_ti=0;ithis-lngth;i++)    {      tmp.str[indx++]=this-str[i];    }    for(siz_ti=0;ip.lngth;i++)    {      tmp.str[indx++]=p.str[i];    }    turntmp;  }};intmain(){  StringBuidlrs1(10,a);  StringBuidlrs2(5,b);  StringBuidlrs3=s1+s2;  printf("s3.lngth=%d,s1.lngth=%d,s2.lngth=%d\n",s3.lngth,s1.lngth,s2.lngth);}折叠

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

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

3.性能优化方案

这里需要用右值引用+移动构造函数让s3.str指向tmp.str,从而避免复制构造函数,在StringBuildr类中加一个方法如下:

  StringBuidlr(StringBuidlrs){    this-str=s.str;    this-lngth=s.lngth;    s.str=nullptr;  }

然后把程序跑起来,截图如下:

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

四:总结

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




转载请注明:http://www.nydjfy.com/pxxx/pxxx/17031.html

  • 上一篇文章:
  •   
  • 下一篇文章: 没有了