在C++11中所有的值一定屬於左值、純右值和將亡值三種值之一,分別介紹一下這三種類型。
左值與右值
在C++中定義左值與右值的比較標準的方法是根據其可以取地址來判斷。左值就是可以對變量進行取地址或者有名字的變量,按照C語言中的規定也就是說其在內存中是被分配了位置;而右值就是不可以取地址、沒有名字的。比如 a = b + c;其中a就是左值、b+c就是右值。
純右值與將亡值
純右值就是C++98中的右值的概念,就是指臨時變量和一些不跟對象關聯的值,比如1+2就是指臨時變量,true和‘c’就是一些和對象不相關的值,還有函數的返回值和lamdba函數等
將亡值指的是與右值引用相關的表達式。比如:std::move()的返回值、類型爲T&&的函數返回值等。
右值引用
上述提到了右值引用,右值引用指的是是右值的引用,由於右值一般不存在名字,可以通過引用的方式延長右值的生命週期。比如說如下的代碼:
T && a = ReturnValue();
ReturnValue()
函數的返回值就是一種右值,通過利用其返回值將a進行初始化,這樣就等價於”延長”了函數返回值的生命週期。
上述代碼中也可以使用如下的方式進行初始化:
T a = ReturnValue();
這種方式相對於第一種方式多了一次對象的構造函數和析構函數的調用,如果返回值佔用的內存比較大,這樣可能會帶來比較高的成本。
注意
- 非常量右值引用不可以綁定任何的左值
- 常量左值引用可以綁定非常量左值、常量左值和右值
- 非常量左值只可以綁定非常量
- 常量右值引用一般沒有用處
std::move()
std::move()函數會強制將左值轉化爲右值引用,等價於一個類型轉換的操作。一般使用這個方法的場景是在構造一個新對象的時候,尤其是這個新對象佔用了很大的內存空間,將一個左值轉化成爲右值引用的時候,可以調用移動構造函數,這樣可以降低對象創建帶來的開銷,如果此類中沒有定義移動構造函數,那麼就會調用這個類的拷貝構造函數。
移動語義
- 默認的移動構造函數與默認的拷貝構造函數相同,只是做按位拷貝的工作
- 在C++11中拷貝構造/賦值和移動構造/賦值函數必須同時提供或者同時不提供。如果自定義拷貝構造函數、拷貝賦值函數、移動賦值函數和析構函數中的一個或者多個,編譯器不會生成默認的移動構造函數。如果自定義移動構造函數、移動賦值函數、拷貝賦值函數和析構函數中的一個或者多個,編譯器不會生成默認的拷貝構造函數
- 對於只聲明移動構造函數/賦值函數的類,通常數屬於資源類型,比如:智能指針、文件流等
- RVO/NRVO表示返回值優化,如果使用參數-fno-elide-constructors這個參數將會關閉該優化;如果沒有關閉該優化,對於如下代碼:
T ReturnRvalue(){ A a(); return a;}
T b = ReturnValue();
其實b 對象所佔用的空間就是a對象的空間。但是RVO/NRVO並不是總是有效的。
完美轉發
之所有存在完美轉發,其問題實質是:模板參數類型推導在轉發過程中無法保證左右值的引用問題。而完美轉發就是在不破壞const屬性的前提下通過增加左右值引用概念和新增參數推導規則解決這個問題。
可以用如下代碼來幫助理解完美轉發:
template <typename T>
void IamForwarding(T t)
{
IrunCodeActually(t);
}
在上述代碼中IamForwarding
會將參數轉發給IrunCodeActually
函數來執行,但是在函數調用之間還會存在對象的拷貝,因此可以使用引用來解決該問題,但是又要考慮左值引用和右值引用的問題。可以使用重載來解決該問題,但是這樣會造成代碼的冗餘。
最終解決這個問題的方法就是使用引用摺疊,在引用摺疊中規定:如果兩個引用中有一個是左值引用,那麼摺疊的結果是一個左值引用。否則(即兩個都是右值引用),摺疊的結果是一個右值引用。,引用摺疊規則如下:
與模板配合使用的例子
#include<iostream>
using namespace std;
void runcode(int && m)
{
cout << "rvalue ref" << endl;
}
void runcode(int & m)
{
cout << "lvalue ref" << endl;
}
void runcode(const int & m)
{
cout << "const lvalue ref" << endl;
}
void runcode(const int && m)
{
cout << "const rvalue ref" << endl;
}
template <typename T>
void PerfectForward(T &&t)
{
runcode(forward<T>(t));
}
int main()
{
int a;
int b;
const int c = 1;
const int d = 0;
PerfectForward(a);
PerfectForward(move(b));
PerfectForward(c);
PerfectForward(move(d));
}