Move assignment operator
ムーブ代入演算子は、同じクラス型の引数で呼び出すことができ、引数の内容をコピーし(場合によっては引数を変更する)可能性のある、 非静的メンバ関数 で、名前が operator = である非テンプレートの関数です。
目次 |
構文
正式なムーブ代入演算子の構文については、 function declaration を参照してください。以下の構文リストは、有効なムーブ代入演算子の構文の一部のみを示しています。
戻り値型
operator=(
パラメータリスト
);
|
(1) | ||||||||
戻り値型
operator=(
パラメータリスト
)
関数本体
|
(2) | ||||||||
戻り値型
operator=(
デフォルト引数なしパラメータリスト
) = default;
|
(3) | ||||||||
戻り値型
operator=(
パラメータリスト
) = delete;
|
(4) | ||||||||
戻り値型
クラス名
::
operator=(
パラメータリスト
)
関数本体
|
(5) | ||||||||
戻り値型
クラス名
::
operator=(
デフォルト引数なしパラメータリスト
) = default;
|
(6) | ||||||||
| class-name | - |
ムーブ代入演算子が宣言されているクラス。クラス型は以下の説明では
T
として与えられる
|
| parameter-list | - |
型
T&&
、
const
T
&&
、
volatile
T
&&
または
const
volatile
T
&&
の1つのパラメータのみからなる
パラメータリスト
|
| parameter-list-no-default | - |
型
T&&
、
const
T
&&
、
volatile
T
&&
または
const
volatile
T
&&
の1つのパラメータのみからなり、デフォルト引数を持たない
パラメータリスト
|
| function-body | - | ムーブ代入演算子の 関数本体 |
| return-type | - |
任意の型、ただしスカラ型との一貫性を保つために
T&
が推奨される
|
説明
struct X { X& operator=(X&& other); // ムーブ代入演算子 // X operator=(const X other); // エラー: 不正なパラメータ型 }; union Y { // ムーブ代入演算子は上記に列挙されていない構文を持つことができます // 一般的な関数宣言の構文に従い // 上記で列挙された制限に違反しない限り auto operator=(Y&& other) -> Y&; // OK: 後置戻り値型 Y& operator=(this Y&& self, Y& other); // OK: 明示的オブジェクトパラメータ // Y& operator=(Y&&, int num = 1); // エラー: 他の非オブジェクトパラメータを持つ };
ムーブ代入演算子は、 オーバーロード解決 によって選択される際に呼び出されます。例えば、オブジェクトが代入式の左辺に現れ、右辺が同じ型または暗黙変換可能な型の右辺値である場合などです。
ムーブ代入演算子は通常、引数が保持するリソース(例:動的に確保されたオブジェクトへのポインタ、ファイル記述子、TCPソケット、スレッドハンドルなど)のコピーを作成するのではなく転送し、引数を有効だが不定な状態にします。ムーブ代入は引数の寿命を変更しないため、後で引数のデストラクタが呼び出されることになります。例えば、 std::string または std::vector からのムーブ代入では、引数が空の状態になる可能性があります。ムーブ代入は通常の代入よりも制限が緩和されて定義されており、通常の代入が完了時にデータの2つのコピーを残さなければならないのに対し、ムーブ代入は1つのコピーのみを残すことが要求されます。
暗黙的に宣言されるムーブ代入演算子
クラス型に対してユーザー定義のムーブ代入演算子が提供されておらず、以下の条件がすべて満たされる場合:
- ユーザー宣言された copy constructor が存在しない;
- ユーザー宣言された move constructor が存在しない;
- ユーザー宣言された copy assignment operator が存在しない;
- ユーザー宣言された destructor が存在しない,
その場合、コンパイラは以下のシグネチャを持つムーブ代入演算子を inline public メンバーとして宣言します: T & T :: operator = ( T && ) 。
クラスは複数のムーブ代入演算子を持つことができます。例えば、
T
&
T
::
operator
=
(
const
T
&&
)
と
T
&
T
::
operator
=
(
T
&&
)
の両方です。ユーザー定義のムーブ代入演算子が存在する場合でも、ユーザーは
default
キーワードを使用して暗黙的に宣言されたムーブ代入演算子の生成を強制することができます。
暗黙的に宣言されるムーブ代入演算子は、以下のように例外仕様を持ちます。 動的例外仕様 (C++17まで) noexcept仕様 (C++17以降) 。
任意のクラスに対して何らかの代入演算子(ムーブまたはコピー)が常に宣言されるため、基底クラスの代入演算子は常に隠蔽されます。using宣言を使用して基底クラスから代入演算子を取り込み、その引数型が派生クラスの暗黙的な代入演算子の引数型と同じである可能性がある場合、using宣言も暗黙的な宣言によって隠蔽されます。
暗黙的に定義されるムーブ代入演算子
暗黙的に宣言されたムーブ代入演算子が削除もトリビアルもされない場合、それが ODR-used または 定数評価に必要とされる (C++14以降) 場合、コンパイラによって定義される(つまり、関数本体が生成されコンパイルされる)。
共用体型の場合、暗黙に定義されるムーブ代入演算子はオブジェクト表現をコピーします( std::memmove による場合と同様に)。
非共用体クラス型の場合、ムーブ代入演算子は、オブジェクトの直接基底と直接の非静的メンバに対して、宣言順に完全なメンバ単位のムーブ代入を実行します。スカラー型には組み込みの代入を、配列にはメンバ単位のムーブ代入を、クラス型にはムーブ代入演算子を(非仮想的に呼び出して)使用します。
|
クラス
|
(C++14 以降)
(C++23 以前) |
|
クラス
|
(C++23 以降) |
コピー代入と同様に、継承格子内で複数のパスを通じてアクセス可能な仮想基底クラスの部分オブジェクトが、暗黙的に定義されたムーブ代入演算子によって複数回代入されるかどうかは未規定です:
struct V { V& operator=(V&& other) { // これは1回または2回呼び出される可能性がある // 2回呼び出された場合、'other'は直前にムーブされたVのサブオブジェクトである return *this; } }; struct A : virtual V {}; // operator= は V::operator= を呼び出す struct B : virtual V {}; // operator= は V::operator= を呼び出す struct C : B, A {}; // operator= は B::operator=、次に A::operator= を呼び出す // しかしそれらは V::operator= を1回しか呼び出さない可能性がある int main() { C c1, c2; c2 = std::move(c1); }
削除されたムーブ代入演算子
T
クラスの暗黙的に宣言された、またはデフォルト化されたムーブ代入演算子は、以下のいずれかの条件が満たされる場合に削除済みとして定義されます:
-
Tが const 修飾された非クラス型(または多次元配列)の非静的データメンバを持つ場合 -
Tが参照型の非静的データメンバを持つ場合 -
Tがクラス型M(または多次元配列)の 潜在的に構築される部分オブジェクト を持ち、Mのムーブ代入演算子を探索するためのオーバーロード解決において
-
- 使用可能な候補が得られない場合、または
- サブオブジェクトが variant member である場合に、非自明な関数を選択する場合。
削除された暗黙的に宣言されたムーブ代入演算子は、 オーバーロード解決 によって無視されます。
自明なムーブ代入演算子
T
クラスのムーブ代入演算子は、以下のすべての条件を満たす場合に自明となります:
- ユーザー提供ではない(つまり、暗黙的に定義されるかデフォルト化されている)こと;
-
Tが仮想メンバ関数を持たないこと; -
Tが仮想基底クラスを持たないこと; -
Tのすべての直接基底クラスに対して選択されるムーブ代入演算子がトリビアルであること; -
Tのすべての非静的クラス型(またはクラス型の配列)メンバに対して選択されるムーブ代入演算子がトリビアルであること。
自明なムーブ代入演算子は、自明なコピー代入演算子と同じ動作を行います。つまり、オブジェクト表現を std::memmove によってコピーするかのように複製します。C言語と互換性のあるすべてのデータ型は自明にムーブ代入可能です。
有効なムーブ代入演算子
|
ムーブ代入演算子は、削除されていない場合に適格です。 |
(C++20まで) |
|
ムーブ代入演算子は、以下の条件がすべて満たされる場合に適格です:
|
(C++20以降) |
有効なムーブ代入演算子の自明性は、クラスが 自明コピー可能型 であるかどうかを決定します。
注記
コピー代入演算子とムーブ代入演算子の両方が提供されている場合、引数が rvalue (無名の一時オブジェクトなどの prvalue や std::move の結果などの xvalue )の場合、オーバーロード解決はムーブ代入を選択し、引数が lvalue (名前付きオブジェクトや左辺値参照を返す関数/演算子)の場合、コピー代入を選択します。コピー代入のみが提供されている場合、すべての引数カテゴリがそれを選択します(rvalueはconst参照にバインドできるため、引数を値またはconst参照として取る限り)。これにより、ムーブが利用できない場合、コピー代入はムーブ代入の代替として機能します。
仮想基底クラスのサブオブジェクトが継承格子内で複数の経路を通じてアクセス可能である場合、それらが暗黙的に定義されたムーブ代入演算子によって複数回代入されるかどうかは未規定である(これは コピー代入 にも同様に適用される)。
ユーザー定義のmove-assignment operatorの期待される動作に関する詳細は、 assignment operator overloading を参照してください。
例
#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++規格に対して遡及的に適用されました。
| DR | 適用バージョン | 公開時の仕様 | 修正後の仕様 |
|---|---|---|---|
| CWG 1353 | C++11 |
デフォルト化されたムーブ代入演算子が削除済みとして定義される条件が
多次元配列型を考慮していなかった |
これらの型を考慮する |
| CWG 1402 | C++11 |
非自明なコピー代入演算子を呼び出すデフォルト化されたムーブ代入演算子は
削除済みとされていた;削除済みのデフォルト化されたムーブ代入演算子は オーバーロード解決に参加していた |
そのようなコピー代入演算子の呼び出しを許可;
オーバーロード解決では無視されるように変更 |
| CWG 1806 | C++11 |
仮想基底クラスを含むデフォルト化されたムーブ代入演算子の
仕様が欠落していた |
追加された |
| CWG 2094 | C++11 |
volatile サブオブジェクトによってデフォルト化された
ムーブ代入演算子が非自明になっていた ( CWG issue 496 ) |
自明性に影響しない |
| CWG 2180 | C++11 |
クラス
T
のデフォルト化されたムーブ代入演算子が
T
が抽象クラスで非ムーブ代入可能な直接の仮想基底クラスを
持つ場合に削除済みとして定義されていなかった |
この場合に演算子は
削除済みとして定義される |
| CWG 2595 | C++20 |
より制約されているが関連制約を満たさない別のムーブ代入演算子が
存在する場合、ムーブ代入演算子が適格ではなかった |
この場合に適格となりうる |
| CWG 2690 | C++11 |
共用体型に対する暗黙的に定義されるムーブ代入演算子が
オブジェクト表現をコピーしていなかった |
オブジェクト表現を
コピーする |