当前位置: 首页 > 科技观察

左值引用、右值引用、移动语义、完美转发,你知道的不知道的都在这里

时间:2023-03-14 21:55:56 科技观察

左值引用,右值引用,移动语义,完美转发,你知道不知道的都在这里Lvaluervalueprvaluewillxvalue左值引用rvaluereferencemovesemantics完美转发返回值优化方案下面喵将一一介绍:左值,右值概念一:左值:可以放在等号左边的东西称为左值。右值:不能放在等号左边的东西称为右值。概念2:左值:可以获取地址并具有名称的事物是左值。右值:没有名称且不能获取地址的东西是右值。例如:inta=b+c;a是一个左值,有一个变量名,可以取地址,也可以放在等号左边,表达式b+c的返回值是一个右值,没有名字,不能取地址,&(b+c)不能编译,不能放在等号左边。inta=4;//a是左值,4是普通字面量的右值。左值一般有:返回左值引用的函数名和变量名。由赋值表达式或赋值运算符连接的表达式(a=b、a+=b等)解引用表达式*pstringliterals"abcd"prvalues、prvalues和prvalues都是右值。纯右值运算表达式生成的临时变量、与对象无关的原始字面量、非引用返回的临时变量、lambda表达式等都是纯右值。例子:字符串字面量以外的字面值返回非引用类型函数调用后自增自减表达式i++、i--算术表达式(a+b、a*b、a&&b、a==b等)得到地址表达式等(&a)XvalueXvalue是指C++11新加入的右值引用相关的表达式,通常指的是要移动的对象,T&&函数的返回值,std::的返回值move函数转换为T&&类型转换函数的返回值。x值可以理解为即将被销毁的值。通过“窃取”其他变量的内存空间得到的值保证其他变量不再被使用。或者在即将被销毁时,可以避免内存空间的释放和分配,延长变量值的生命周期,常用于完成移动构造或移动赋值等特殊任务。例子:classA{xxx;};Aa;autoc=std::move(a);//c是缺值autod=static_cast(a);//d是缺值左值引用,右值引用的意思可以根据名字猜到。左值引用是对左值的引用类型,右值引用是对右值的引用类型。它们都是引用,对象的别名,并没有自己所有的Binding对象都存储在堆上,所以所有的都必须立即初始化。type&name=exp;//lvaluereferencetype&&name=exp;//rvaluereferencelvaluereferenceseecode:inta=5;int&b=a;//bislvaluereferenceb=4;int&c=10;//error,10can't取地址,不能做引用constint&d=10;//ok,因为是常量引用,引用了一个常量数,这个常量数会存入内存,取地址可以画一个结论:对于左值引用,等号右边的值一定能取到地址。如果取不到地址,编译就会失败,或者可以使用const引用形式,但是这样只能通过引用读取输出,不能修改数组,因为它是常量引用。如果使用右值引用,则表达式等号右侧的值需要为右值,可使用std::move函数强制将左值转换为右值。inta=4;int&&b=a;//error,a是左值int&&c=std::move(a);//okmove语义在讲move语义之前,我们首先要了解深拷贝和浅拷贝的概念深拷贝、浅拷贝直接以代码为例:classA{public:A(intsize):size_(size){data_=newint[size];}A(){}A(constA&a){size_=a.size_;data_=a.data_;cout<<"copy"<data_=a.data_;a.data_=nullptr;cout<<"移动"<vecs;...std::vectorvecm=std::move(vecs);//移除大量副本注意:move语义只针对那些实现了moveconstructor类的对象对int、float等基本类型没有任何优化作用,仍然会被复制,因为它们没有对应的move构造函数。完美转发完美转发是指你可以编写一个函数模板,它可以接受任何实际参数并将其转发给其他函数。目标函数将接收与转发函数相同的实参。如果转发函数的实参是左值,那么目标函数的实参也将是左值。value,转发函数的实参是右值,目标函数的实参也是右值。那么如何实现完美转发呢,答案就是使用std::forward()。voidPrintV(int&t){cout<<"lvalue"<voidTest(T&&t){PrintV(t);PrintV(std::forward(t));PrintV(std::move(t));}intmain(){Test(1);//lvaluervaluervalueinta=1;Test(a);//lvaluervaluervalueTest(std::forward(a));//lvaluervaluervalueTest(std::forward(a));//lvaluervaluervalueTest(std::forward(a));//lvaluervaluervaluereturn0;}分析测试(1):1是右值,模板中的T&&t是通用引用,右值1传给Test函数就变成了右值引用,但是当调用PrintV()时,t变成了左值,因为它变成了一个带有a的变量name,所以打印左值,而PrintV(std::forward(t))时,会进行完美转发,按照原来的类型进行转发,所以打印右值,PrintV(std::move(t))打印右值毫无疑问。Test(a):a是左值,模板中的T&&是通用引用,左值a传递给Test函数成为左值引用,所以在代码中打印出来。Test(std::forward(a)):作为左值还是右值转发取决于T。如果T是左值,它将作为左值转发。如果T是右值,它将作为右值转发。返回值优化返回值优化(RVO)是一种C++编译优化技术。当一个函数需要返回一个对象实例时,会创建一个临时对象,并通过复制构造函数将目标对象复制到临时对象中。这里复制构造函数和析构函数会被冗余调用,这是有代价的,通过返回值优化,C++标准允许省略对这些复制构造函数的调用。那么编译器什么时候进行返回值优化呢?return的值类型与函数的返回值类型相同。返回是一个本地对象。请看几个示例:示例1:std::vectorreturn_vector(void){std::vectortmp{1,2,3,4,5};returntmp;}std::vector&&rval_ref=return_vector();不会触发RVO,copy构造一个临时对象,临时对象的生命周期绑定到rval_ref,相当于下面的代码:conststd::vector&rval_ref=return_vector();示例2:std::vector&&return_vector(void){std::vectortmp{1,2,3,4,5};returnstd::move(tmp);}std::vector&&rval_ref=return_vector();此代码将导致运行时错误,因为rval_ref引用了Thedestructedtmp。按理说,这段代码是错误的,但是我自己运行成功了。我就没那么幸运,这里就不纠结了,继续往下看什么时候会触发RVO。示例3:std::vectorreturn_vector(void){std::vectortmp{1,2,3,4,5};returnstd::move(tmp);}std::vector&&rval_ref=return_vector();与例1类似,std::move一个临时对象不是必需的,返回值优化将被忽略。最佳代码:std::vectorreturn_vector(void){std::vectortmp{1,2,3,4,5};returntmp;}std::vectorrval_ref=return_vector();这段代码会触发RVO,既不复制也不移动,不会产生临时对象。