C++11新標准中的一個最主要的特性就是移動而非拷貝對象的能力。接下來簡要介紹一下相關概念。
-
右值引用
-
所謂右值引用就是必須綁定到右值的引用。通過 && 而不是 & 來獲得右值引用。右值引用有一個重要的性質 — 只能綁定到一個將要銷毀的對象。因此我們可以自由的將一個右值引用的資源“移動”到另一個對象中。
-
一般而言,一個左值表達式表示的是一個對象的身份,而一個右值表達式表示的是對象的值。
-
類似任何引用,一個右值引用也不過是某個對象的一個名字而已。對於一個常規引用(左值引用),不能將其綁定到要求轉換的表達式、字面常量或是返回右值的表達式。右值引用則相反:可以將一個右值引用綁定到這類表達式上,但不能將一個右值引用直接綁定到一個左值上。
int i = 42; int &r = i; //正確, r 引用 i, 左值引用 int &&r = i; //錯誤,不能將一個右值引用綁定到一個左值上 int &r2 = i * 42; //錯誤, i * 42 是一個右值 const int &r3 = i * 42; //正確,可以將一個const的引用綁定到一個右值上 int &&rr2 = i * 42; //正確,將rr2綁定到乘法結果上
1. 返回左值引用的函數,連同賦值、下標、解引用和前置遞增、遞減運算符,都是返回左值的表達式的例子。 2. 返回非引用類型的函數,連同算術、關系、位以及后置遞增、遞減運算符,都生成右值。我們不能將一個左值引用綁定到這類表達式上,但我們可以將一個const的左值引用和一個右值引用綁定到這類表達式上。
-
左值持久,右值短暫
- 左值有持久的狀態,而右值要么是字面常量,要么是在表達式求值過程中創建的臨時對象。
- 右值引用指向將要被銷毀的對象。因此,可以從綁定到右值引用的對象“竊取”狀態,即使用右值引用的代碼可以自由接管所引用的對象的資源。
-
變量是左值,不能將一個右值引用直接綁定到一個變量上,即使這個變量是右值引用類型也不行。
int &&rr1 = 42; //正確,字面常量是右值 int &&rr2 = rr1; //錯誤,表達式rr1是左值
-
標准庫的 move 函數
- 雖然不能將一個右值直接綁定到一個左值上,但可以顯式地將一個左值轉換為對應的右值引用類型。我們可以通過調用一個名為 move的新標准庫函數來獲得綁定到左值上的引用。頭文件
int &&rr3 = std::move(rr1); //ok
- 我們可以銷毀一個移后源對象,也可以賦予它新值,但不能使用一個移后源對象的值。
- 雖然不能將一個右值直接綁定到一個左值上,但可以顯式地將一個左值轉換為對應的右值引用類型。我們可以通過調用一個名為 move的新標准庫函數來獲得綁定到左值上的引用。頭文件
-
-
移動構造函數和移動賦值運算符
- 類似拷貝構造函數,移動構造函數的第一個參數是該類類型的一個引用。不同於拷貝構造函數的是,這個引用參數在移動構造函數中是一個右值引用。與拷貝構造函數相同,任何額外的參數都必須有默認實參。
- 除了完成資源移動,移動構造函數還必須確保移后源對象處於這樣一個狀態 — 銷毀它是無害的。特別是,一旦資源完成移動,源對象必須不再指向被移動的資源。這些資源的所有權已經歸屬於新創建的對象。
- 移動操作、標准庫容器和異常
- 由於移動操作“竊取”資源,它通常不會分配任何資源。因此,移動操作通常不會拋出任何異常。
- 當編寫一個不拋出異常的移動操作時,我們應該將此事通知標准庫。除非標准庫知道不會拋出異常,否則它會為了處理可能拋出異常這種可能性而做一些額外的工作。
- 一種通知標准庫的方法是將構造函數指明為 noexcept。這個關鍵字是新標准引入的。
- 不拋出異常的移動構造函數和移動賦值運算符都必須標記為noexcept.
-
移后源對象必須可析構
- 從一個對象移動數據並不會銷毀此對象,但有時在移動操作完成后,源對象會被銷毀。當我們編寫一個移動操作時,必須確保移后源對象進入一個可析構的狀態。即將移后源對象的指針成員置為nullptr來實現的。
- 在移動操作之后,移后源對象必須保持有效的、可析構的狀態,但是用戶不能對其值進行任何假設。
-
合成的移動操作
- 如果一個類定義了自己的拷貝構造函數、拷貝賦值運算符或者析構函數,編譯器就不會為它合成移動構造函數和移動賦值運算符了。
- 只有當一個類沒有定義任何自己版本的拷貝控制成員,且類的每個非static數據都可以移動時,編譯器才會為它合成移動構造函數和移動賦值運算符。
- 定義了一個移動構造函數或移動賦值運算符的類必須也定義自己的拷貝操作。否則,這些成員默認被定義為刪除的。