移動建構函式
移動建構函式是一種建構函式,它能以同類型別的右值實參呼叫,並複製該實參的內容,可能會修改實參。
目錄 |
[編輯] 語法
類名 ( 形參列表 ); |
(1) | ||||||||
類名 ( 形參列表 ) 函式體 |
(2) | ||||||||
類名 ( 單形參列表 ) = default; |
(3) | ||||||||
類名 ( 形參列表 ) = delete; |
(4) | ||||||||
類名 :: 類名 ( 形參列表 ) 函式體 |
(5) | ||||||||
類名 :: 類名 ( 單形參列表 ) = default; |
(6) | ||||||||
類名 | - | 要宣告其移動建構函式的類 |
形參列表 | - | 滿足以下所有條件的非空形參列表
|
單形參列表 | - | 只含一個形參的形參列表,該形參的型別是 T&&、const T&&、volatile T&& 或 const volatile T&&,且不帶預設實參 |
函式體 | - | 移動建構函式的函式體 |
[編輯] 解釋
struct X { X(X&& other); // move constructor // X(X other); // Error: incorrect parameter type }; union Y { Y(Y&& other, int num = 1); // move constructor with multiple parameters // Y(Y&& other, int num); // Error: `num` has no default argument };
移動建構函式通常在物件從同類型的右值(純右值或亡值)(C++17 前)亡值(C++17 起)初始化(透過直接初始化或複製初始化)時呼叫,包括:
- 初始化:T a = std::move(b); 或 T a(std::move(b));,其中 b 的型別是
T
; - 函式實參傳遞:f(std::move(a));,其中 a 的型別是
T
且 f 是 void f(T t); - 函式返回:在諸如 T f() 的函式中 return a;,其中 a 的型別是擁有移動建構函式的
T
。
當初始化器是純右值時,移動建構函式的呼叫通常會被最佳化掉(C++17 前)從不發生(C++17 起),見複製消除。
移動建構函式通常“竊取”實參所持有的資源(例如指向動態分配物件的指標、檔案描述符、TCP 套接字、執行緒控制代碼等),而不是複製它們,並讓實參處於某種有效但未指明的狀態。因為移動建構函式不改變實參的生存期,所以實參的解構函式通常會在稍後被呼叫。例如,從 std::string 或 std::vector 移動可能會導致實參變為空。對於某些型別,例如 std::unique_ptr,被移動後的狀態是完全指定的。
[編輯] 隱式宣告的移動建構函式
若沒有為類型別提供使用者定義的移動建構函式,且以下各項全部為真
那麼編譯器將宣告一個移動建構函式,作為其類的非 explicit 的 inline public 成員,其簽名為 T::T(T&&)。
一個類可以有多個移動建構函式,例如 T::T(const T&&) 和 T::T(T&&)。如果存在某些使用者定義的移動建構函式,使用者仍可以使用關鍵詞 default 強制生成隱式宣告的移動建構函式。
隱式宣告(或在其首次宣告時被設為預設)的移動建構函式擁有一個異常規定,如動態異常規定(C++17 前)noexcept 規定(C++17 起)中所述。
[編輯] 隱式定義的移動建構函式
若隱式宣告的移動建構函式既未被刪除也非平凡,則當它被 ODR 式使用或為常量求值所需時,編譯器會定義它(即生成並編譯一個函式體)。對於聯合體型別,隱式定義的移動建構函式複製其物件表示(如同用 std::memmove)。對於非聯合體的類型別,移動建構函式以其初始化順序,對物件的直接基類子物件和成員子物件進行逐成員的移動,這透過以一個亡值實參進行直接初始化來完成。對於每個引用型別的非靜態資料成員,移動建構函式將該引用繫結到源引用所繫結的同一物件或函式。
如果這滿足constexpr
建構函式(C++23 前)constexpr
函式(C++23 起)的要求,那麼生成的移動建構函式是 constexpr 的。
[編輯] 被刪除的移動建構函式
類 T
的隱式宣告或顯式預設的移動建構函式被定義為已刪除的,如果 T
擁有一個類型別 M
的潛在構造的子物件(或其多維陣列),使得:
-
M
有一個已刪除的或從複製建構函式不可訪問的解構函式,或者 - 為尋找
M
的移動建構函式而進行的過載決議
- 未產生可用的候選,或者
- 在子物件是變體成員的情況下,選擇了一個非平凡函式。
這樣的建構函式會被過載決議忽略(否則它會阻止從右值進行的複製初始化)。
[編輯] 平凡移動建構函式
類 T
的移動建構函式是平凡的,如果以下各項全部為真
- 它不是使用者提供的(意即,它是隱式定義或被設為預設的);
-
T
沒有虛成員函式; -
T
沒有虛基類; - 為
T
的每個直接基類所選擇的移動建構函式都是平凡的; - 為
T
的每個非靜態類型別(或類型別的陣列)成員所選擇的移動建構函式都是平凡的。
平凡移動建構函式是一種建構函式,它與平凡複製建構函式執行相同的動作,即如同用 std::memmove 一樣複製物件表示。所有與 C 語言相容的資料型別都是可平凡移動的。
[編輯] 合格的移動建構函式
一個移動建構函式如果是合格的,那麼它未被刪除。 |
(C++20 前) |
一個移動建構函式是合格的,如果滿足以下所有條件: |
(C++20 起) |
合格的移動建構函式的平凡性決定了該類是否為隱式生存期型別,以及該類是否為可平凡複製型別。
[編輯] 注意
為使強異常保證成為可能,使用者定義的移動建構函式不應丟擲異常。例如,std::vector 在需要重分配元素時,依賴 std::move_if_noexcept 來在移動和複製之間做出選擇。
若同時提供了複製和移動建構函式,且無其他建構函式可行,則當實參是同類型的右值時(亡值,如 std::move 的結果,或純右值,如無名臨時量(C++17 前)),過載決議會選擇移動建構函式;而當實參是左值時(有物件或返回左值引用的函式/運算子),則選擇複製建構函式。若只提供了複製建構函式,則所有實參類別都會選擇它(只要它接受 const 引用,因為右值可以繫結到 const 引用),這使得在移動不可用時,複製成為移動的備選方案。
[編輯] 示例
#include <iomanip> #include <iostream> #include <string> #include <utility> struct A { std::string s; int k; A() : s("test"), k(-1) {} A(const A& o) : s(o.s), k(o.k) { std::cout << "move failed!\n"; } A(A&& o) noexcept : s(std::move(o.s)), // explicit move of a member of class type k(std::exchange(o.k, 0)) // explicit move of a member of non-class type {} }; A f(A a) { return a; } struct B : A { std::string s2; int n; // implicit move constructor B::(B&&) // calls A's move constructor // calls s2's move constructor // and makes a bitwise copy of n }; struct C : B { ~C() {} // destructor prevents implicit move constructor C::(C&&) }; struct D : B { D() {} ~D() {} // destructor would prevent implicit move constructor D::(D&&) D(D&&) = default; // forces a move constructor anyway }; int main() { std::cout << "Trying to move A\n"; A a1 = f(A()); // return by value move-constructs the target // from the function parameter std::cout << "Before move, a1.s = " << std::quoted(a1.s) << " a1.k = " << a1.k << '\n'; A a2 = std::move(a1); // move-constructs from xvalue std::cout << "After move, a1.s = " << std::quoted(a1.s) << " a1.k = " << a1.k << '\n'; std::cout << "\nTrying to move B\n"; B b1; std::cout << "Before move, b1.s = " << std::quoted(b1.s) << "\n"; B b2 = std::move(b1); // calls implicit move constructor std::cout << "After move, b1.s = " << std::quoted(b1.s) << "\n"; std::cout << "\nTrying to move C\n"; C c1; C c2 = std::move(c1); // calls copy constructor std::cout << "\nTrying to move D\n"; D d1; D d2 = std::move(d1); }
輸出
Trying to move A Before move, a1.s = "test" a1.k = -1 After move, a1.s = "" a1.k = 0 Trying to move B Before move, b1.s = "test" After move, b1.s = "" Trying to move C move failed! Trying to move D
[編輯] 缺陷報告
下列更改行為的缺陷報告追溯地應用於以前出版的 C++ 標準。
缺陷報告 | 應用於 | 釋出時的行為 | 正確的行為 |
---|---|---|---|
CWG 1353 | C++11 | 預設移動建構函式被定義為 已刪除的條件沒有考慮多維陣列型別 |
考慮這些型別 |
CWG 1402 | C++11 | 會呼叫非平凡複製建構函式的 預設移動建構函式被定義為已刪除; 一個被刪除的預設移動建構函式 仍會參與過載決議 |
允許呼叫這樣的複製 建構函式;使其被過載 決議忽略 |
CWG 1491 | C++11 | 帶有右值引用型別非靜態資料成員的 類的預設移動建構函式被定義為已刪除 |
在這種情況下不被刪除 |
CWG 2094 | C++11 | 一個 volatile 子物件使得一個預設的 移動建構函式變為非平凡(CWG 問題 496) |
平凡性不受影響 |
CWG 2595 | C++20 | 如果存在另一個更受約束的移動建構函式, 那麼一個移動建構函式就不合格 則複製建構函式不合格 |
它在這種情況下可以合格 |