浅析C++11中的右值引用、转移语义和完美转发

2020-01-06 15:44:07丽君

1. 左值与右值:

    C++对于左值和右值没有标准定义,但是有一个被广泛认同的说法:可以取地址的,有名字的,非临时的就是左值;不能取地址的,没有名字的,临时的就是右值.

    可见立即数,函数返回的值等都是右值;而非匿名对象(包括变量),函数返回的引用,const对象等都是左值.

    从本质上理解,创建和销毁由编译器幕后控制的,程序员只能确保在本行代码有效的,就是右值(包括立即数);而用户创建的,通过作用域规则可知其生存期的,就是左值(包括函数返回的局部变量的引用以及const对象),例如:


int& foo(){int tmp; return tmp;}

int fooo(){int tmp; return tmp;}

int a=10;

const int b;

int& temp=foo();//虽然合法,但temp引用了一个已经不存在的对象

int tempp=fooo();

以上代码中,a,temp和foo()都是非常量左值,b是常量左值,fooo()是非常量右值,10是常量右值,有一点要特别注意:返回的引用是左值(可以取地址)!

一般来说,编译器是不允许对右值进行更改的(因为右值的生存期不由程序员掌握,即使更改了右值也未必可以用),对于内置类型对象尤其如此,但C++允许使用右值对象调用成员函数,虽然允许这样做,但出于同样原因,最好不要这么做.

2. 右值引用:

    右值引用的表示方法为


 Datatype&& variable

    右值引用是C++ 11新增的特性,所以C++ 98的引用为左值引用.右值引用用来绑定到右值,绑定到右值以后本来会被销毁的右值的生存期会延长至与绑定到它的右值引用的生存期,右值引用的存在并不是为了取代左值引用,而是充分利用右值(特别是临时对象)的建构来减少对象建构和析构操作以达到提高效率的目的,例如对于以下函数:


(Demo是一个类)
Demo foo(){ 
  Demo tmp;
  return tmp;
}

在编译器不进行RVO(return value optimization)优化的前提下以下操作:


Demo x=foo();

将会调用三次构造函数(tmp的,x的,临时对象的),相应的在对象被销毁时也会调用三次析构函数,而如果采用右值引用的方式:


Demo&& x=foo();

那么就不需要进行x的建构,本来本来要被销毁的临时对象也会由于x的绑定而将生存期延长至和x一样(可以理解为x赋予了那个临时对象一个合法地位:一个名字),就需要提高了效率(代价就是tmp需要占据4字节空间,但这是微不足道的).

    右值引用与左值引用绑定规则:

         常量左值引用可以绑定到常量和非常量左值,常量和非常量右值;