移動賦值運算子
移動賦值運算子是一個非模板非靜態成員函式,其名稱為 operator=,可以接受相同類型別的引數,並複製引數的內容,可能會修改引數。
目錄 |
[編輯] 語法
有關正式的移動賦值運算子語法,請參閱函式宣告。下面的語法列表僅演示了所有有效移動賦值運算子語法的一個子集。
返回型別 operator=( 引數列表 ); |
(1) | ||||||||
返回型別 operator=( 引數列表 ) 函式體 |
(2) | ||||||||
返回型別 operator=( 無預設引數列表 ) = default; |
(3) | ||||||||
返回型別 operator=( 引數列表 ) = delete; |
(4) | ||||||||
返回型別 類名 :: operator=( 引數列表 ) 函式體 |
(5) | ||||||||
返回型別 類名 :: operator=( 無預設引數列表 ) = default; |
(6) | ||||||||
類名 | - | 正在宣告其移動賦值運算子的類,在下面的描述中,類型別以 T 表示 |
引數列表 | - | 僅包含一個引數的引數列表,該引數的型別為 T&& 、const T&&、volatile T&& 或 const volatile T&& |
無預設引數列表 | - | 僅包含一個引數的引數列表,該引數的型別為 T&& 、const T&&、volatile T&& 或 const volatile T&& 且不具有預設實參 |
函式體 | - | 移動賦值運算子的函式體 |
返回型別 | - | 任何型別,但為了與 Scala 型別保持一致,推薦使用 T& |
[編輯] 解釋
struct X { X& operator=(X&& other); // move assignment operator // X operator=(const X other); // Error: incorrect parameter type }; union Y { // move assignment operators can have syntaxes not listed above, // as long as they follow the general function declaration syntax // and do not viloate the restrictions listed above auto operator=(Y&& other) -> Y&; // OK: trailing return type Y& operator=(this Y&& self, Y& other); // OK: explicit object parameter // Y& operator=(Y&&, int num = 1); // Error: has other non-object parameters };
當過載決議選中移動賦值運算子時,它會被呼叫,例如,當一個物件出現在賦值表示式的左側,而右側是相同或隱式可轉換型別的右值時。
移動賦值運算子通常會轉移引數持有的資源(例如,指向動態分配物件的指標、檔案描述符、TCP 套接字、執行緒控制代碼等),而不是複製它們,並使引數處於某種有效但未確定的狀態。由於移動賦值不會改變引數的生命週期,因此解構函式通常會在稍後對引數進行呼叫。例如,從 std::string 或 std::vector 進行移動賦值可能會導致引數為空。移動賦值的定義比普通賦值的限制更少,而不是更多;普通賦值在完成時必須留下兩份資料副本,而移動賦值只需要留下一份。
[編輯] 隱式宣告的移動賦值運算子
如果對於類型別沒有提供使用者定義的移動賦值運算子,並且以下所有條件都為真:
那麼編譯器將宣告一個移動賦值運算子作為其類的 inline public 成員,其簽名為 T& T::operator=(T&&)。
一個類可以有多個移動賦值運算子,例如 T& T::operator=(const T&&) 和 T& T::operator=(T&&)。如果存在一些使用者定義的移動賦值運算子,使用者仍然可以使用關鍵字 default
強制生成隱式宣告的移動賦值運算子。
隱式宣告的移動賦值運算子具有異常規範,如 動態異常規範(C++17 前)noexcept 規範(C++17 起) 中所述。
由於對於任何類總是聲明瞭某個賦值運算子(移動或複製),因此基類賦值運算子總是被隱藏。如果使用 using 宣告將基類中的賦值運算子引入,並且其引數型別可能與派生類的隱式賦值運算子的引數型別相同,則 using 宣告也會被隱式宣告隱藏。
[編輯] 隱式定義的移動賦值運算子
如果隱式宣告的移動賦值運算子既不被刪除也不平凡,那麼當它被odr-使用或需要用於常量求值(C++14 起)時,編譯器會定義它(即生成並編譯函式體)。
對於聯合型別,隱式定義的移動賦值運算子複製物件表示(如同透過 std::memmove)。
對於非聯合類型別,移動賦值運算子按照宣告順序對其物件的直接基類和即時非靜態成員執行完全成員級移動賦值,對標量使用內建賦值,對陣列使用成員級移動賦值,對類型別使用移動賦值運算子(非虛呼叫)。
如果滿足以下條件,則類
|
(C++14 起) (直至 C++23) |
類 |
(C++23 起) |
與複製賦值一樣,隱式定義的移動賦值運算子是否多次賦值透過繼承層次結構中多於一個路徑可訪問的虛基類子物件是未指定的。
struct V { V& operator=(V&& other) { // this may be called once or twice // if called twice, 'other' is the just-moved-from V subobject return *this; } }; struct A : virtual V {}; // operator= calls V::operator= struct B : virtual V {}; // operator= calls V::operator= struct C : B, A {}; // operator= calls B::operator=, then A::operator= // but they may only call V::operator= once int main() { C c1, c2; c2 = std::move(c1); }
[編輯] 已刪除的移動賦值運算子
類 T
的隱式宣告或預設的移動賦值運算子在滿足以下任何條件時被定義為已刪除:
-
T
具有 const 限定的非類型別(或可能是多維陣列)的非靜態資料成員。 -
T
具有引用型別的非靜態資料成員。 -
T
具有類型別M
(或可能是多維陣列)的潛在構造子物件,以至於應用於查詢M
的移動賦值運算子的過載決議
- 未產生可用的候選,或者
- 在子物件是變體成員的情況下,選擇了一個非平凡函式。
已刪除的隱式宣告移動賦值運算子在過載決議中被忽略。
[編輯] 平凡移動賦值運算子
如果滿足以下所有條件,類 T
的移動賦值運算子是平凡的:
- 它不是使用者提供的(意味著它是隱式定義或預設的);
-
T
沒有虛成員函式; -
T
沒有虛基類; - 為
T
的每個直接基類選擇的移動賦值運算子是平凡的; - 為
T
的每個非靜態類型別(或類型別陣列)成員選擇的移動賦值運算子是平凡的。
平凡移動賦值運算子執行與平凡複製賦值運算子相同的操作,即複製物件表示,如同透過 std::memmove。所有與 C 語言相容的資料型別都是可平凡移動賦值的。
[編輯] 合格的移動賦值運算子
如果移動賦值運算子未被刪除,則它是合格的。 |
(C++20 前) |
如果滿足以下所有條件,移動賦值運算子是合格的: |
(C++20 起) |
合格移動賦值運算子的平凡性決定了該類是否是可平凡複製型別。
[編輯] 注意
如果同時提供了複製和移動賦值運算子,當引數是右值(無論是像無名臨時物件這樣的純右值,還是像 std::move 結果這樣的將亡值)時,過載決議選擇移動賦值;當引數是左值(命名物件或返回左值引用的函式/運算子)時,選擇複製賦值。如果只提供了複製賦值,所有引數類別都會選擇它(只要它透過值或作為 const 引用獲取引數,因為右值可以繫結到 const 引用),這使得複製賦值在移動不可用時成為移動賦值的備選方案。
隱式定義的移動賦值運算子是否多次賦值透過繼承層次結構中多於一個路徑可訪問的虛基類子物件是未指定的(這同樣適用於複製賦值)。
有關使用者定義的移動賦值運算子的預期行為的更多詳細資訊,請參閱賦值運算子過載。
[編輯] 示例
#include <iostream> #include <string> #include <utility> struct A { std::string s; A() : s("test") {} A(const A& o) : s(o.s) { std::cout << "move failed!\n"; } A(A&& o) : s(std::move(o.s)) {} A& operator=(const A& other) { s = other.s; std::cout << "copy assigned\n"; return *this; } A& operator=(A&& other) { s = std::move(other.s); std::cout << "move assigned\n"; return *this; } }; A f(A a) { return a; } struct B : A { std::string s2; int n; // implicit move assignment operator B& B::operator=(B&&) // calls A's move assignment operator // calls s2's move assignment operator // and makes a bitwise copy of n }; struct C : B { ~C() {} // destructor prevents implicit move assignment }; struct D : B { D() {} ~D() {} // destructor would prevent implicit move assignment D& operator=(D&&) = default; // force a move assignment anyway }; int main() { A a1, a2; std::cout << "Trying to move-assign A from rvalue temporary\n"; a1 = f(A()); // move-assignment from rvalue temporary std::cout << "Trying to move-assign A from xvalue\n"; a2 = std::move(a1); // move-assignment from xvalue std::cout << "\nTrying to move-assign B\n"; B b1, b2; std::cout << "Before move, b1.s = \"" << b1.s << "\"\n"; b2 = std::move(b1); // calls implicit move assignment std::cout << "After move, b1.s = \"" << b1.s << "\"\n"; std::cout << "\nTrying to move-assign C\n"; C c1, c2; c2 = std::move(c1); // calls the copy assignment operator std::cout << "\nTrying to move-assign D\n"; D d1, d2; d2 = std::move(d1); }
輸出
Trying to move-assign A from rvalue temporary move assigned Trying to move-assign A from xvalue move assigned Trying to move-assign B Before move, b1.s = "test" move assigned After move, b1.s = "" Trying to move-assign C copy assigned Trying to move-assign D move assigned
[編輯] 缺陷報告
下列更改行為的缺陷報告追溯地應用於以前出版的 C++ 標準。
缺陷報告 | 應用於 | 釋出時的行為 | 正確的行為 |
---|---|---|---|
CWG 1353 | C++11 | 預設的移動賦值運算子被定義為已刪除的條件 未考慮多維陣列型別 |
考慮這些型別 |
CWG 1402 | C++11 | 一個預設的移動賦值運算子會 呼叫一個非平凡的複製賦值運算子,然後被 刪除;一個預設的移動賦值運算子 被刪除後仍參與過載決議 |
允許呼叫此類 複製賦值 運算子;在過載決議中 被忽略 |
CWG 1806 | C++11 | 涉及虛基類的預設移動賦值運算子的規範 缺失 |
已新增 |
CWG 2094 | C++11 | 一個 volatile 子物件導致預設的 移動賦值運算子變為非平凡(CWG 問題 496) |
平凡性不受影響 |
CWG 2180 | C++11 | 如果類 T 是抽象的並且具有不可移動賦值的直接虛基類,則類 T 的預設移動賦值運算子未被定義為已刪除 |
在這種情況下,該運算子 被定義為已刪除 |
CWG 2595 | C++20 | 如果存在另一個移動賦值運算子,其約束更強 但未滿足其關聯約束,則移動賦值運算子不合格 在這種情況下它可以合格 |
它在這種情況下可以合格 |
CWG 2690 | C++11 | 聯合型別的隱式定義移動賦值運算子 未複製物件表示 |
它們複製物件 表示 |