目錄
概念:
左值引用和右值引用
概念:
注意:
左值引用的意義
作函數參數
函數引用返回
右值引用的意義
誕生背景
移動構造
移動賦值
其他應用
萬能引用和完美轉發
默認的移動構造和移動賦值
概念:
左值:顧名思義,可以在等號左邊出現的被叫做左值,左值可以取地址。
右值:只能在等號右邊出現的值為右值,且右值不能取地址。
例如
//左值 int a = 2; string s="hello"; const int b=3; int* pa=&a; //右值 3;//字面常量 a + 2;//表達式 add(2, 3);//函數返回值
左值引用和右值引用
概念:
左值引用是用來引用左值的,給左值起別名:
int a = 2;//左值 int& ra = a;//左值引用
右值引用是用來引用右值的,給右值起別名:
int&& rc = 5;//右值引用
注意:
左值引用只能引用左值,因為如果引用右值會導致權限的放大:
int& ra = 2;//權限放大,編譯錯誤
但是左值引用加上const就可以引用右值:
const int& ra = 2;
右值引用只能引用右值,但是可以引用move后的左值
int a = 2;//左值 int&& ra = move(a);
左值引用的意義
作函數參數
C++引用底層是C語言的指針,所以在作為函數參數時可以減少拷貝或者作為輸出型參數使用。
void func(const string& s)//輸入型參數,const&既可以減少拷貝又可以防止改變引用的實體 {cout << s << endl; } void add(int& a)//作為輸出型參數 {a=2; }
函數引用返回
出了函數棧幀,返回變量不會銷毀的就可以用引用返回,減少拷貝。否則會導致內存越界。
string ss="hello"; const string& func() {return ss;//ss是全局變量,出函數棧幀不會銷毀,用引用返回就不會生成臨時變量 }
右值引用的意義
誕生背景
普通函數傳值返回時,由于函數棧幀出作用域會銷毀,其內定義的變量會被釋放,所以會返回一個臨時對象,這個臨時對象是要返回對象的拷貝。
此時,我們會想:既然局部變量s馬上就要銷毀了,那么我們是否可以將它的資源交換給臨時變量呢,這樣臨時變量的創建就不需要耗費拷貝的代價了。
C++把這種即將要銷毀的自定義對象稱為將亡值,在拷貝將亡值的時,可以利用右值引用在拷貝構造和賦值運算符重載函數中接收將亡值,并實現資源的置換,進而減少無意義的拷貝。
移動構造
在拷貝構造的實現中,實現一份形參使用右值引用接收的拷貝構造(即移動構造),當使用將亡值拷貝構造時,編譯器會自動匹配移動構造,在移動構造內實現資源的互換。
移動賦值
在賦值運算符重載的實現中,實現一份形參使用右值引用接收的賦值運算符重載(即移動賦值),當使用將亡值賦值時,編譯器會自動匹配移動賦值,在移動賦值內實現資源的互換。
C++11后,基本所有的STL容器都新增了移動構造和移動賦值,以此來提高需要深拷貝的容器是將亡值時的拷貝效率。
其他應用
除此之外,右值引用在其他接口中也大放異彩:
STL容器插入數據的接口:當插入需要深拷貝對象的右值時,減少拷貝
std::swap交換函數:
萬能引用和完美轉發
當函數模板和右值引用組合:萬能引用既可以引用左值,又可以引用右值
萬能引用又叫做引用折疊,即傳入右值時&&,傳入左值時折疊&。
右值引用的作用就是限制了接收變量只能是右值,但是右值引用本身會退化為左值:
template<class T>
void func(T&& val)//萬能引用
{cout << val << endl;//就算原本接收的是右值,右值引用val退化為左值
}
int main()
{func(10);return 0;
}
完美轉發:保留對象原生類型屬性,即保持它的左值或者右值的屬性
template<class T>
void func(T&& val)//萬能引用
{cout << std::forward<T>(val) << endl;//這里使用完美轉發保留傳入之前的屬性
}
int main()
{func(10);return 0;
}
默認的移動構造和移動賦值
默認移動構造:
如果你沒有自己實現移動構造函數,且沒有實現析構函數 、拷貝構造、拷貝賦值重載中的任
意一個。那么編譯器會自動生成一個默認移動構造。默認生成的移動構造函數,對于內置類
型成員會執行逐成員按字節拷貝,自定義類型成員,則需要看這個成員是否實現移動構造,
如果實現了就調用移動構造,沒有實現就調用拷貝構造。
解釋:
分析上述定義,默認移動構造就是在析構函數 、拷貝構造、賦值重載都沒有寫,且移動構造也沒有顯式實現的情況下,編譯器會默認生成。
當析構函數,拷貝構造、賦值重載都沒有寫的時候,且默認C++程序員沒有犯錯,那么這個類本身一定只含有內置類型成員或"自控"的自定義類型成員,沒有需要手動釋放的資源。
此時編譯器就要做事了,默認生成一份移動構造,當別人調用默認移動構造,來將自定義類型中可以移動構造的對象自動調用移動構造,增加效率,非常合理。
反著思考:如果一個類內有資源需要手動釋放,那么析構函數 、拷貝構造、賦值重載一定都要手動寫,此時就算我們不手動寫移動構造,編譯器也不會默認生成,因為編譯器不會知道哪些資源需要轉移。
默認移動賦值完全類似。