天天看點

右值引用和對象移動右值引用對象移動

右值引用

右值

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());
}
           

代碼說明:

  1. 測試代碼第9行用X類型的左值 a 來測試move函數,根據标準X類型的右值引用 b 隻能綁定X類型的右值,是以 move(a) 的傳回值必然是X類型的右值。
  2. 測試代碼第10行用X類型的右值 X() 來測試move函數,根據标準X類型的右值引用 c 隻能綁定X類型的右值,是以 move(X()) 的傳回值必然是X類型的右值。
  3. 首先我們來分析 move(a) 這種用左值參數來調用move函數的情況。
  4. 模拟單步調用來到源碼第3行,_Tp&& ≡ X&, __t  ≡ a 。
  5. 根據函數模闆參數推導規則,_Tp&& ≡ X& 可推出 _Tp ≡ X& 。
  6. typename std::remove_reference<_Tp>::type ≡ X 。
  7. typename std::remove_reference<_Tp>::type&& ≡ X&& 。
  8. 再次單步調用進入move函數實體所在的源碼第4行。
  9. static_cast<typename std::remove_reference<_Tp>::type&&>(__t) ≡ static_cast<X&&>(a)
  10. 根據标準 static_cast<X&&>(a) 将把左值 a 轉換為X類型的無名右值引用。
  11. 然後我們再來分析 move(X()) 這種用右值參數來調用move函數的情況。
  12. 模拟單步調用來到源碼第3行,_Tp&& ≡ X&&, __t  ≡ X() 。
  13. 根據函數模闆參數推導規則,_Tp&& ≡ X&& 可推出 _Tp ≡ X 。
  14. typename std::remove_reference<_Tp>::type ≡ X 。
  15. typename std::remove_reference<_Tp>::type&& ≡ X&& 。
  16. 再次單步調用進入move函數實體所在的源碼第4行。
  17. static_cast<typename std::remove_reference<_Tp>::type&&>(__t) ≡ static_cast<X&&>(X())
  18. 根據标準 static_cast<X&&>(X()) 将把右值 X() 轉換為X類型的無名右值引用。
  19. 由9和16可知源碼中std::move函數的具體實作符合标準,
  20. 因為無論用左值a還是右值X()做參數來調用std::move函數,
  21. 該實作都将傳回無名的右值引用(右值的一種),符合标準中該函數的定義。

繼續閱讀