名稱空間
變體
操作

移動建構函式

來自 cppreference.com
< cpp‎ | 語言
 
 
C++ 語言
通用主題
流程控制
條件執行語句
if
迭代語句(迴圈)
跳轉語句
函式
函式宣告
Lambda 函式表示式
inline 說明符
動態異常規範 (直到 C++17*)
noexcept 說明符 (C++11)
異常
名稱空間
型別
說明符
const/volatile
decltype (C++11)
auto (C++11)
constexpr (C++11)
consteval (C++20)
constinit (C++20)
儲存期說明符
初始化
 
 

移動建構函式是一種建構函式,它能以同類型別的右值實參呼叫,並複製該實參的內容,可能會修改實參。

目錄

[編輯] 語法

類名 (形參列表 ); (1)
類名 (形參列表 ) 函式體 (2)
類名 (單形參列表 ) = default; (3)
類名 (形參列表 ) = delete; (4)
類名 ::類名 (形參列表 ) 函式體 (5)
類名 ::類名 (單形參列表 ) = default; (6)
類名 - 要宣告其移動建構函式的類
形參列表 - 滿足以下所有條件的非空形參列表
  • 給定類型別為 T,首個形參的型別是 T&&const T&&volatile T&&const volatile T&&,並且
  • 要麼沒有其他形參,要麼所有其他形參都有預設實參
單形參列表 - 只含一個形參的形參列表,該形參的型別是 T&&const T&&volatile T&&const volatile T&&,且不帶預設實參
函式體 - 移動建構函式的函式體

[編輯] 解釋

1) 類定義內的移動建構函式宣告。
2-4) 類定義內的移動建構函式定義。
3) 移動建構函式被顯式預設。
4) 移動建構函式被刪除。
5,6) 類定義外的移動建構函式定義(該類必須包含一個宣告 (1))。
6) 移動建構函式被顯式預設。
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 的型別是 Tfvoid f(T t)
  • 函式返回:在諸如 T f() 的函式中 return a;,其中 a 的型別是擁有移動建構函式的 T

當初始化器是純右值時,移動建構函式的呼叫通常會被最佳化掉(C++17 前)從不發生(C++17 起),見複製消除

移動建構函式通常“竊取”實參所持有的資源(例如指向動態分配物件的指標、檔案描述符、TCP 套接字、執行緒控制代碼等),而不是複製它們,並讓實參處於某種有效但未指明的狀態。因為移動建構函式不改變實參的生存期,所以實參的解構函式通常會在稍後被呼叫。例如,從 std::stringstd::vector 移動可能會導致實參變為空。對於某些型別,例如 std::unique_ptr,被移動後的狀態是完全指定的。

[編輯] 隱式宣告的移動建構函式

若沒有為類型別提供使用者定義的移動建構函式,且以下各項全部為真

那麼編譯器將宣告一個移動建構函式,作為其類的非 explicitinline 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 如果存在另一個更受約束的移動建構函式,
那麼一個移動建構函式就不合格
則複製建構函式不合格
它在這種情況下可以合格

[編輯] 參閱