C++11(2):
我们今天接着讲C++11的内容,在讲新的内容之前,我们先回顾一下我们之前许过的内容,
我们上节课学习了右值引用和移动语义:
我们说右值的生命周期只有一行,这行结束以后,右值的生命周期就结束了,这里的话,我们可以把右值比喻成一个身体不好生命快要结束的人,左值的话我们可以比喻为身体健康,生命不会很快的结束的人。
所以对于深拷贝的类型的话,我们就可以增加一个右值引用的移动构造和移动赋值,之前的话,我们走的都是左值的拷贝构造和拷贝赋值,我们实现的函数参数是const 修饰的左值,这样的话我们的右值也可以走这条路,。
但是现在我们的移动构造,移动赋值和拷贝构造,拷贝赋值构成函数重载,如果是我们的右值的话,我们上面讲了,右值相当于是生命快要结束的人,这时候我们想要右值的数据,我们如果走拷贝构造的话,我们就是开和右值一样大的空间,然后把右值的数据拷贝过来,然后销毁右值,,,这样的消耗是比较大的,,我们如果实现了移动构造的话,我们的右值可以走移动构造,我们就直接把数据进行了交换,就好比是生命快结束的病人希望把自己的器官捐献下来,让那些需要这个器官的病人不需要使用人工造的器官,可以直接使用这个人留下来的,这样就比较好。
右值的话一般是临时对象和匿名对象,一般这行结束以后就要析构销毁,那我们可以把它里面的数据直接和我们的值的数据进行交换,这样的话消耗比较小。
那么move的话,我们说是把左值强行转换为右值,我们上节讲到说这个是比较危险的,因为他有可能让你的数据丢失。
旧的我们复习了,我们开始新的知识的学习:
引用延长生命周期:
我们说了我们创建的临时对象和匿名对象的右值他的生命周期都只在当前的这一行。
我们这里的话,我们说可以使用const 左值引用或着右值引用来引用右值的话,他会延长的生命周期。
我们来看一下:
看上面的代码和结果,一行******的上面我们的写了一个匿名对象,我们看结果,一行******的上面就是构造完后析构,右值的生命周期只有一行。
我们继续往下看:
这下我们把这个匿名对象使用const左值引用修饰,他的生命周期得到了延长,他的生命周期就是跟着r1走了,直到这个函数结束以后,r1被销毁,匿名对象的生命周期才结束。
再往下看:
我们看这个,我们再使用右值引用来修饰匿名对象,也是把他的生命周期延长,他的生命周期就是看r2的了,函数结束以后,r2销毁,这个匿名对象也就结束了。
我们往下看:
我们上节课在讲解左值引用和右值引用的时候,我们遗留下来两个点,我们说下节讲:
我们看,我们把后面的两点留了下来:
我们现在来看后面的两点:
我们说我们的左值的话走的是拷贝构造,你要老老实实的进行拷贝数据,但是如果是右值的话,我们可以走移动构造,把右值的数据进行转移,但是这里的话就有一个问题,,我们说要把右值的数据进行转移,但是转移的话,我们就要修改右值的里面的数据为普通的数据,把有效的数据换出去,这是不是就是把我们的右值给修改了,,
但是我们上节讲过,我们的右值的话是不能修改的,右值一般为临时对象或者匿名对象,具有常性不可修改。
这似乎就是一个悖论,我们既要转移右值的数据,又不能修改右值,,,这怎么办,我们就看我们的图中的讲解:
我们说,语法上认为,变量表达式都是左值。
我们这个图片,r1和r2都是左值。但是r1的话是使用const修饰的,他不能被修改,r2可以。
所以说,我们的右值虽然不能被直接的修改,但是如果右值被左值引用了就可以被修改了。
我们继续往下看:
我们看这个,这个也是我们上节遗留的点,我们现在知道了右值引用表达之本身的属性是左值,我们看上面的图片:
我们构造一个匿名对象,右值的话调用移动构造,进入到移动构造函数以后,我们的参数s是右值,我们把它传进到swap函数里面的时候,这个swap函数的参数是一个左值,这是怎么回事呢?
因为我们的右值引用表达式的实际的属性是左值。所以我们这里可以传进去。
我们继续往下看:
右值引用和移动语义在容器插入中的提效:
C++11我们的库里面的容器的插入函数之类的也增加了右值引用的版本。提高了效率;
重要:
右值引用的属性本身是一个左值;
我们看这个图片,当我们自己实现push_back()函数的时候,因为我们上面说过,表达式的左边我们认为他的属性都是左值,,这时候函数里面x虽然进来是右值,但是他是一个表达式,他的属性就变成了左值,左值的话,传到insert函数里面的的时候就进到左值引用参数的函数里面了。
我们给他强行move转成右值,才能进入到我们的右值引用的insert函数里面。
所以在我们自己实现push_back和insert的这种右值引用的版本的时候,我们要记得时时刻刻要加上move进行转化:
x传进来就是右值,但是因为这是一个右值引用表达式,他的属性就成了左值,我们就要给他进行move强转成右值。
引用折叠:
引用折叠就是引用的引用,两个引用叠加到一起了。
我们看上面的图片,正常的情况下,我们的三个引用写在一起是有问题的,这种不存在,但是如果typedef一下的话,这就有可能产生引用叠加的情况:
我们看上面的情况:我们引用折叠的话就有一些规定:
左值引用和左值引用折叠得到的是左值引用,左值引用和右值引用折叠得到的是左值引用,右值引用和左值引用得到的是左值引用,最后右值引用和右值引用得到的是右值引用。
当我们加上模板以后:
我们看上面的图片:当我们的模板函数我们设置为T&的时候,我们实例化成int类型,最后会成为int&,如果实例化成int&类型的时候,他的结果是int&类型的,如果是int&&的时候,结果是int&类型的,如果最后实例化成const int&的时候,结果是const int&。
这个的话,无论我们的T实例化成什么类型,这个函数模板实例化以后的结果始终是左值引用
我们再看这个:
这个的话,我们的模板传过去的是右值引用,然后我们实例化普通的int类型过去的话,他的结果是int&&类型的,如果传过去的是int&类型的,结果就是int&,类型的,如果传过去的是int&&类型的,结果就是int&&类型的。
这个版本的话,我们实例化后可以是左值引用也可以是右值引用。
其实的话,我们的这个右值引用我们可以把它叫做---万能引用:
万能引用:
我们看这个图片:我们现在调用这个模板函数,我们不给他实例化,我们直接传入数据让他自己推演,我们看上面的图片,当我们传一个字面量(右值)进去的时候,他推演出来是一个int,然后实例化的结果就是右值引用。
万能引用就是我们传左值进去他实例化的就是左值引用,如果传右值进去的话,就是右值引用。
我们再往下看:
我们刚才是不是写了这两个push_back()的函数,我们上面的是左值引用没问题,那么我们看下面这个是右值引用还是万能引用,
这个push_back()是一个右值引用,那这里为什么不是万能引用了呢?
我们看这个万能引用的话,他是一个函数模板,他的参数t的类型是通过实参传递推出来的。
但是这个不是万能引用;
这两个函数是我们类模板的成员函数;
这里的函数参数T不是由我们的传过来的实参的类型决定的,这个T早在类模板实例化的时候就实例化好了,这时候就推不了了。
只有说是函数模板去推参数类型的时候,他才是万能引用。
这个尾插函数除非你给他改成函数模板,这时候你可以传左值,也可以传右值。他进行推参数的类型,这个就是万能引用。
所以说我们看就要看他是不是函数模板,参数类型是不是靠实参来推的,是的话就是万能引用,如果说不是函数模板,参数类型也是早都实例化好了的,不靠实参推演,这就不是万能引用。
我们继续往下看:
完美转发:
我们看这个代码,这就是一个万能引用,函数模板,可以传左值,也可以传右值。
我们看这个代码:
首先是四个全局函数,这四个全局函数构成函数重载,当参数类型是左值的时候进入到第一个函数,打印“左值引用”,当参数类型是const 左值引用的时候,就进入到第二个函数,打印,,,,
然后我们有一个万能引用的函数模板,当我们调用这个函数的时候,我们可以传左值,也可以传右值,然后这个函数模板就会推演出这个t参数的类型。
然后在Function函数里面,我们有调用Fun函数,我们把参数t传进去。
但是:
上面的图片我们看见了,我们在main函数里面,我们传了左值,也传了右值,但是我们最终打印的结果居然都是左值引用。
这是为什么呢?
因为我们上面讲了,我们右值引用的表达式是可以修改右值的。
我们的右值引用本身是一个左值,所以进入到函数里面的话,t的属性就是一个左值,那么不管我们传右值进来,还是传左值进来,他的结果都是一个左值属性。
这就导致了最终我们的上面全都是左值引用。
右
那又有什么办法可以保持他的属性呢?
之前的话,我们右值引用进来的时候,由于右值引用本身的属性是一个左值,所以进到函数以后,x的属性就是左值,我们要保持他的右值属性,我们可以给他来一个move强转成右值。
但是我们这里的话就不太行了,
这里这是一个函数模板,这是一个泛型编程,我们之前是在右值引用的函数里面实行这个,但是这里的话是所有的类型都是要走这里的,我们要是给这个函数模板move的话,那就全变成右值引用了。
我们的要求是保持他的属性,move就是全部变成右值属性了,左值成右值,右值也成了右值。
那怎么办呢?
我们的库里面提供了一个函数----完美转发;forward;
这个函数的含义就是你是左值我就保持你的左值的属性,你是右值我就保持你右值的属性。
但是我们的完美转发也不是说就是直接是这样来使用,因为我们上面说了,你是左值我就保持你的左值的属性,你是右值我就保持你右值的属性,但是这里的t他进到函数里面就是左值是属性。
他这样还是保持的左值的属性没有变。
我们要这样使用,显式实例化的去使用,进到函数里面的时候我们的函数模板就推出了T是左值类型的还是右值类型的。
完美转发和万能引用结合着使用;