右值引用
右值
C++( 包括 C) 中所有的表達式和變量要麼是左值,要麼是右值。通俗的左值的定義就是非臨時對象,那些可以在多條語句中使用的對象。 所有的變量都滿足這個定義,在多條代碼中都可以使用,都是左值。 右值是指臨時的對象,它們隻在目前的語句中有效。
右值引用概念
為了支援移動操作,C++11引入了一種新的引用類型-右值引用。就是必須綁定到右值的引用。我們通過&&而不是&來獲得右值引用。右值引用隻能綁定到一個将要銷毀的對象。
由于右值引用隻能綁定到臨時對象,我們得知:
所引用的對象将要被銷毀
該對象沒有其他使用者
這兩個特性意味着:使用右值引用的代碼可以自身自由的接管所引用的對象的資源。
變量可以看作隻有一個運算對象而沒有運算符的表達式。
int i = 23;
int &&j = i;//錯誤:不能将一個右值引用綁定到一個左值上
int &&k = i * 3;//正确,i*3是一個右值
标準庫move函數
move函數了解之前先要知道左值、左值引用、右值、右值引用的概念。
雖然不能将一個右值引用直接綁定到一個左值上,但可以通過調用一個名為move的新标準庫函數來獲得綁定到左值上的右值引用。
int i=6;
int &&l=std::move(i);
對比下面操作
第二段代碼使用了move語句,而第一段代碼沒有使用move語句。輸出的結果差異也很明顯,第二段代碼中,原來的字元串st已經為空,而第一段代碼中,原來的字元串st的内容沒有變化。
int main()
{
string st = "I'm a superman";
vector<string> vc;
vc.push_back(st);
cout << vc[0] << endl;
if (!st.empty())
cout << st << endl;
system("pause");
return 0;
}
int main()
{
string st = "I'm a superman";
vector<string> vc;
vc.push_back(move(st));
cout << vc[0] << endl;
if (!st.empty())
cout << st << endl;
system("pause");
return 0;
}
對象移動
提出
在很多情況下都會發生對象拷貝,比如利用reallocate重新配置設定記憶體的過程,在其中某些情況下,對象拷貝後就立即被銷毀了。在這些情況下,移動而非拷貝對象會大幅度提升性能。
移動構造函數和移動指派運算符
移動構造函數,參數是右值引用
StrVec::StrVec(StrVec &&s) noexcept //noexcept指名不抛出異常
:elements(s.elements), first_free(s.first_free),cap(s.cap)
{
s.elements = s.first_free = s.cap = nullptr; //銷毀地動源後的對象
}
與拷貝構造函數不同,移動構造函數不配置設定任何新記憶體:它接管給定的StrVec中的記憶體。在接管記憶體後,它将給定對象中的指針都置為nullptr。
合成的移動操作
與處理拷貝構造函數和拷貝指派運算符一樣,編譯器也會合成移動構造函數和移動指派運算符。但是,合成移動構造的條件與合成拷貝操作的條件大不相同。
隻有當一個類沒有定義任何自己版本的拷貝控制成員(拷貝構造函數、拷貝指派運算符、析構函數),且它的所有資料成員都能移動構造或移動指派時,編譯器才會為它合成移動構造函數或移動指派運算符。
如果一個類定義了一個移動構造函數或一個移動指派運算符,則該類的合成拷貝構造函數和拷貝指派運算符被定義為删除的。
右值引用和成員函數
除了構造函數和指派運算符之外,成員函數也可以同時提供拷貝版本和移動版本。例如:
void push_back(const X&); //拷貝:綁定到任意類型的X
void push_back(X&&); //移動:隻能綁定到類型X的可修改的右值
關于此部分講解很好的博文:https://www.cnblogs.com/qingergege/p/7607089.html
move源碼實作(轉載自:原文位址)
預備知識
1.引用折疊規則:
X& + & => X&
X&& + & => X&
X& + && => X&
X&& + && => X&&
2.函數模闆參數推導規則(右值引用參數部分):當函數模闆的模闆參數為T而函數形參為T&&(右值引用)時适用本規則。
若實參為左值 U& ,則模闆參數 T 應推導為引用類型 U& 。
(根據引用折疊規則, U& + && => U&, 而T&& ≡ U&,故T ≡ U& )
若實參為右值 U&& ,則模闆參數 T 應推導為非引用類型 U 。
(根據引用折疊規則, U或U&& + && => U&&, 而T&& ≡ U&&,故T ≡ U或U&&,這裡強制規定T ≡ U )
3.std::remove_reference為C++0x标準庫中的元函數,其功能為去除類型中的引用。
std::remove_reference<U&>::type ≡ U
std::remove_reference<U&&>::type ≡ U
std::remove_reference<U>::type ≡ U
4.以下文法形式将把表達式 t 轉換為T類型的右值(準确的說是無名右值引用,是右值的一種)
static_cast<T&&>(t)
5.無名的右值引用是右值
具名的右值引用是左值。
6.注:本文中 ≡ 含義為“即,等價于“。
std::move
函數功能:
std::move(t) 負責将表達式 t 轉換為右值,使用這一轉換意味着你不再關心 t 的内容,它可以通過被移動(竊取)來解決移動語意問題。
源碼與測試代碼
template<typename _Tp>
inline typename std::remove_reference<_Tp>::type&&
move(_Tp&& __t)
{ return static_cast<typename std::remove_reference<_Tp>::type&&>(__t); }
#include<iostream>
using namespace std;
struct X {};
int main()
{
X a;
X&& b = move(a);
X&& c = move(X());
}
代碼說明:
- 測試代碼第9行用X類型的左值 a 來測試move函數,根據标準X類型的右值引用 b 隻能綁定X類型的右值,是以 move(a) 的傳回值必然是X類型的右值。
- 測試代碼第10行用X類型的右值 X() 來測試move函數,根據标準X類型的右值引用 c 隻能綁定X類型的右值,是以 move(X()) 的傳回值必然是X類型的右值。
- 首先我們來分析 move(a) 這種用左值參數來調用move函數的情況。
- 模拟單步調用來到源碼第3行,_Tp&& ≡ X&, __t ≡ a 。
- 根據函數模闆參數推導規則,_Tp&& ≡ X& 可推出 _Tp ≡ X& 。
- typename std::remove_reference<_Tp>::type ≡ X 。
- typename std::remove_reference<_Tp>::type&& ≡ X&& 。
- 再次單步調用進入move函數實體所在的源碼第4行。
- static_cast<typename std::remove_reference<_Tp>::type&&>(__t) ≡ static_cast<X&&>(a)
- 根據标準 static_cast<X&&>(a) 将把左值 a 轉換為X類型的無名右值引用。
- 然後我們再來分析 move(X()) 這種用右值參數來調用move函數的情況。
- 模拟單步調用來到源碼第3行,_Tp&& ≡ X&&, __t ≡ X() 。
- 根據函數模闆參數推導規則,_Tp&& ≡ X&& 可推出 _Tp ≡ X 。
- typename std::remove_reference<_Tp>::type ≡ X 。
- typename std::remove_reference<_Tp>::type&& ≡ X&& 。
- 再次單步調用進入move函數實體所在的源碼第4行。
- static_cast<typename std::remove_reference<_Tp>::type&&>(__t) ≡ static_cast<X&&>(X())
- 根據标準 static_cast<X&&>(X()) 将把右值 X() 轉換為X類型的無名右值引用。
- 由9和16可知源碼中std::move函數的具體實作符合标準,
- 因為無論用左值a還是右值X()做參數來調用std::move函數,
- 該實作都将傳回無名的右值引用(右值的一種),符合标準中該函數的定義。