ムーブ代入演算子
ムーブ代入演算子とは、`operator=` という名前の、テンプレートではない、非静的メンバ関数であり、同じクラス型の引数で呼び出すことができ、引数の内容を(引数を変更する可能性を伴って)コピーするものです。
目次 |
[編集] 構文
ムーブ代入演算子の正式な構文については、関数宣言を参照してください。以下の構文リストは、有効なムーブ代入演算子の構文のサブセットのみを示しています。
return-type operator=(parameter-list ); |
(1) | ||||||||
return-type operator=(parameter-list ) function-body |
(2) | ||||||||
return-type operator=(parameter-list-no-default ) = default; |
(3) | ||||||||
return-type operator=(parameter-list ) = delete; |
(4) | ||||||||
return-type class-name ::operator=(parameter-list ) function-body |
(5) | ||||||||
return-type class-name ::operator=(parameter-list-no-default ) = 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つだけであるパラメータリストで、デフォルト引数を持たないもの。 |
| 関数本体 | - | ムーブ代入演算子の関数本体 |
| return-type | - | 任意の型ですが、スカラー型との一貫性を保つために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 };
ムーブ代入演算子は、オーバーロード解決によって選択された場合に呼び出されます。例えば、オブジェクトが代入式の左辺にあり、右辺が同じ型または暗黙変換可能な型のrvalueである場合です。
ムーブ代入演算子は、通常、それらのコピーを作成するのではなく、引数が保持するリソース(例: 動的に割り当てられたオブジェクトへのポインタ、ファイルディスクリプタ、TCPソケット、スレッドハンドルなど)を転送し、引数を有効だが、それ以外では不定な状態のままにします。ムーブ代入は引数のライフタイムを変更しないため、引数に対しては後でデストラクタが呼び出されることになります。例えば、std::stringやstd::vectorからムーブ代入すると、引数が空のままになる可能性があります。ムーブ代入は、通常の代入よりも、制限が少ない(より厳密ではない)定義です。通常の代入は完了時にデータの2つのコピーを残す必要がありますが、ムーブ代入は1つのコピーのみを残すことが要求されます。
[編集] 暗黙宣言されるムーブ代入演算子
クラス型に対してユーザー定義のムーブ代入演算子が提供されておらず、かつ以下のすべてが真である場合。
- ユーザー宣言されたコピーコンストラクタがない。
- ユーザー宣言されたムーブコンストラクタがない。
- ユーザー宣言されたコピー代入演算子がない。
- ユーザー宣言されたデストラクタがない。
コンパイラは、そのクラスのinline publicメンバとして、T& T::operator=(T&&)というシグネチャのムーブ代入演算子を宣言します。
クラスは複数のムーブ代入演算子を持つことができます。例えば、T& T::operator=(const T&&)とT& T::operator=(T&&)の両方です。ユーザー定義のムーブ代入演算子がいくつか存在する場合、ユーザーはdefaultキーワードを使用して、暗黙宣言されるムーブ代入演算子の生成を強制することもできます。
暗黙宣言されるムーブ代入演算子は、動的例外指定(C++17まで)noexcept指定(C++17以降)で説明されている例外仕様を持ちます。
すべてのクラスには常に何らかの代入演算子(ムーブまたはコピー)が宣言されるため、基底クラスの代入演算子は常に隠蔽されます。using宣言を使用して基底クラスの代入演算子を持ち込んだ場合、その引数型が派生クラスの暗黙の代入演算子の引数型と同じになる可能性があると、using宣言も暗黙の宣言によって隠蔽されます。
[編集] 暗黙定義されるムーブ代入演算子
暗黙宣言されるムーブ代入演算子が削除されておらず、トリビアルでもない場合、ODR-useまたは定数評価のために必要(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以降) |
適格なムーブ代入演算子のトリビアル性は、クラスがトリビアルにコピー可能な型であるかどうかを決定します。
[編集] 注記
コピー代入演算子とムーブ代入演算子の両方が提供されている場合、オーバーロード解決は、引数がrvalue(名前のない一時オブジェクトのようなprvalue、またはstd::moveの結果のようなxvalue)である場合にムーブ代入を選択し、引数がlvalue(名前付きオブジェクトまたはlvalue参照を返す関数/演算子)である場合にコピー代入を選択します。コピー代入のみが提供されている場合、すべての引数カテゴリがそれを選択します(引数を値またはconst参照で受け取る限り、rvalueは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++標準に遡って適用されました。
| 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 | 削除されるように修正されました。 ムーブ代入演算子は、より 制約が厳しい別のムーブ代入演算子が存在するが、その関連制約を満たさない場合、 |
この場合でも適格になりうる。 |
| 適格ではありませんでした。 | C++11 | CWG 2690 共用体型の暗黙定義されるムーブ代入演算子は、オブジェクト表現をコピーしていませんでした。 |
オブジェクト 表現をコピーするようになりました。 |