最近在看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++的值类型优先的基因决定的。