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; }
Demo x=foo();
Demo&& x=foo();
class Demo{ public: Demo():p(new int[10000]{}; Demo(Demo&& lre):arr(lre.arr),size(lra.size){lre.arr=NULL;}//转移构造函数 Demo(const Demo& lre):arr(new int[10000]),size(arr.size){ for(int cou=0;cou<10000;++cou) arr[cou]=lew.arr[cou]; } private: int size; int* arr; }
从以上代码可以看出,拷贝构造函数在堆中重新开辟了一个大小为10000的int型数组,然后每个元素分别拷贝,而转移构造函数则是直接接管参数的指针所指向的资源,效率搞下立判!需要注意的是转移构造函数实参必须是右值,一般是临时对象,如函数的返回值等,对于此类临时对象一般在当行代码之后就被销毁,而采用转移构造函数可以延长其生命期,可谓是物尽其用,同时有避免了重新开辟数组.对于上述代码中的转移构造函数,有必要详细分析一下:
Demo(Demo&& lre):arr(lre.arr),size(lre.size)({lre.arr=NULL;}
lre是一个右值引用,通过它间接访问实参(临时对象)的资源来完成资源转移,lre绑定的对象(必须)是右值,但lre本身是左值;
因为lre是函数的局部对象,”lre.arr=NULL"必不可少,否则函数结尾调用析构函数销毁lre时仍然会将资源释放,转移的资源还是被系统收回.
4. move()函数
3中的例子并非万能,Demo(Demo&& lre)的实参必须是右值,有时候一个左值即将到达生存期,但是仍然想要使用转移语义接管它的资源,这时就需要move函数.
std::move函数定义在标准库
5. 完美转发(perfect forwarding)
完美转发指的是将一组实参"完美"地传递给形参,完美指的是参数的const属性与左右值属性不变,例如在进行函数包装的时候,func函数存在下列重载:
void func(const int); void func(int); void func(int&&);
如果要将它们包装到一个函数cover内,以实现:
void cover(typename para){ func(para); }
使得针对不同实参能在cover内调用相应类型的函数,似乎只能通过对cover进行函数重载,这使代码变得冗繁,另一种方法就是使用函数模板,但在C++ 11之前,实现该功能的函数模板只能采用值传递,如下:
template<typename T> void cover(T para){ ... func(para); ... }
但如果传递的是一个相当大的对象,又会造成效率问题,要通过引用传递实现形参与实参的完美匹配(包裹const属性与左右值属性的完美匹配),就要使用C++ 11 新引入的引用折叠规则:
函数形参 T的类型 推导后的函数形参
T& A& A&
T& A&& A&
T&& A& A&
T&& A&& A&&
因此,对于前例的函数包装要求,采用以下模板就可以解决:
template<typename T> void cover(T&& para){ ... func(static_cast<T &&>(para)); ... }
如果传入的是左值引用,转发函数将被实例化为:
void func(T& && para){ func(static_cast<T& &&>(para)); }
应用引用折叠,就为:
void func(T& para){ func(static_cast<T&>(para)); }
如果传入的是右值引用,转发函数将被实例化为:
void func(T&& &¶){ func(static_cast<T&& &&>(para)); }
应用引用折叠,就是:
void func(T&& para){ func(static_cast<T&&>(para)); }
对于以上的static_cast
所以最终版本
template<typename T> void cover(T&& para){ func(forward(forward<T>(para))); }
std::forward的实现与static_cast
std::forward函数的用法为forward
总结
以上就是关于C++11中右值引用、转移语义和完美转发的全部内容,这篇文章介绍的很详细,希望对大家的学习工作能有所帮助。
更多浅析C++11中的右值引用、转移语义和完美转发相关文章请关注PHP中文网!