ムーブ代入演算子
クラス T
のムーブ代入演算子は、 T&&、 const T&&、 volatile T&& または const volatile T&& 型の引数をちょうど1個取る operator= という名前の非テンプレート非静的メンバ関数です。
目次 |
[編集] 構文
class_name & class_name :: operator= ( class_name && )
|
(1) | (C++11以上) | |||||||
class_name & class_name :: operator= ( class_name && ) = default;
|
(2) | (C++11以上) | |||||||
class_name & class_name :: operator= ( class_name && ) = delete;
|
(3) | (C++11以上) | |||||||
[編集] 説明
- ムーブ代入演算子の一般的な宣言。
- ムーブ代入演算子をコンパイラに強制的に生成させます。
- 暗黙のムーブ代入を回避します。
ムーブ代入演算子は、オブジェクトが代入式の左側に現れ、その右側が同じ型または暗黙に変換可能な型の右辺値であるときなどに、オーバーロード解決によって選択されたときに、呼ばれます。
ムーブ代入演算子は、一般的には、引数が保持しているリソース (動的に確保されたオブジェクトへのポインタ、ファイルディスクリプタ、 TCP ソケット、入出力ストリーム、実行中のスレッド、など) をコピーするのではなく「盗み」、その引数を何らかの有効だけれどもそれ以外の点では不定な状態に置きます。 例えば、 std::string や std::vector のムーブ代入は引数を空にするかもしれません。 しかしこれは保証ではありません。 ムーブ代入は普通の代入より制限が少なく (多くではなく) 定義されます。 普通の代入は完全なデータのコピーを2つ残さなければなりませんが、ムーブ代入は1つ残すことが要求されるだけです。
[編集] 暗黙に宣言されたムーブ代入演算子
クラス型 (struct、 class または union) に対してユーザ定義されたムーブ代入演算子が提供されず、以下のすべてが真の場合、
- ユーザ宣言されたコピーコンストラクタがない。
- ユーザ宣言されたムーブコンストラクタがない。
- ユーザ宣言されたコピー代入演算子がない。
- ユーザ宣言されたデストラクタがない。
|
(C++14未満) |
コンパイラはそのクラスの inline public
メンバとしてシグネチャ T& T::operator=(T&&)
を持つムーブ代入演算子を宣言します。
クラスは複数のムーブ代入演算子、例えば T& T::operator=(const T&&) と T& T::operator=(T&&) の両方を持つことができます。 何らかのユーザ定義されたムーブ代入演算子が存在する場合でも、ユーザはキーワード default
を用いて暗黙に宣言されたムーブ代入演算子の生成を強制できます。
暗黙に宣言された (またはその最初の宣言においてデフォルト化された) ムーブ代入演算子は、動的例外指定 (C++17未満)例外指定 (C++17以上)で説明されている通りの例外指定を持ちます。
いかなるクラスに対しても何らかの代入演算子 (ムーブまたはコピー) が必ず宣言されるため、基底クラスの代入演算子は必ず隠蔽されます。 基底クラスの代入演算子を取り込むために using 宣言が使用され、その引数の型が派生クラスの暗黙の代入演算子の引数の型と同じである場合は、その using 宣言も暗黙の宣言によって隠蔽されます。
[編集] 削除された暗黙に宣言されたムーブ代入演算子
クラス T
に対する暗黙に宣言されたまたはデフォルト化されたムーブ代入演算子は、以下のいずれかが真の場合、削除されたものとして定義されます。
-
T
が const である非静的データメンバを持つ。 -
T
が参照型の非静的データメンバを持つ。 -
T
がムーブ代入できない (削除されている、アクセス不可能な、または曖昧なムーブ代入演算子を持つ) 非静的データメンバを持つ。 -
T
がムーブ代入できない (削除されている、アクセス不可能な、または曖昧なムーブ代入演算子を持つ) 直接または仮想の基底クラスを持つ。
|
(C++14未満) |
削除された暗黙に宣言されたムーブ代入演算子はオーバーロード解決では無視されます。 |
(C++14以上) |
[編集] トリビアルなムーブ代入演算子
以下のすべてが真の場合、クラス T
に対するムーブ代入演算子はトリビアルです。
- ユーザ提供されていない (つまり、暗黙に定義されている、またはデフォルト化されている)。
-
T
が仮想メンバ関数を持たない。 -
T
が仮想基底クラスを持たない。 -
T
のすべての直接の基底について、選択されたムーブ代入演算子がトリビアルである。 -
T
のすべてのクラス型 (またはクラスの配列型) の非静的メンバについて、選択されたムーブ代入演算子がトリビアルである。
|
(C++14以上) |
トリビアルなムーブ代入演算子はトリビアルなコピー代入演算子と同じ動作、つまり、 std::memmove によって行われたかのようにオブジェクト表現のコピーを行います。 C 言語と互換性のあるすべてのデータ型 (POD 型) はトリビアルにムーブ代入可能です。
[編集] 暗黙に定義されたムーブ代入演算子
暗黙に宣言されたムーブ代入演算子が削除されておらずトリビアルでもない場合は、 ODR 使用された場合、コンパイラによって定義されます (つまり、関数の本体が生成され、コンパイルされます)。
union 型の場合、暗黙に定義されたムーブ代入演算子は (std::memmove によって行われたかのように) オブジェクト表現をコピーします。
非 union クラス型 (class および struct) の場合、ムーブ代入演算子はオブジェクトの直接の基底および直接の非静的メンバの完全なメンバ単位のムーブ代入を、その宣言の順で、スカラーのついては組み込みの代入を配列についてはメンバ単位のムーブ代入をクラス型の場合はムーブ代入演算子を用いて (非仮想的に呼んで)、行います。
コピー代入と同様に、継承階層内の複数の経路でアクセス可能な仮想基底クラスの部分オブジェクトが、暗黙に定義されたムーブ代入演算子によって複数回代入されるかどうかは、未規定です。 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); } |
(C++14以上) |
[編集] ノート
コピーとムーブ両方の代入演算子が提供されている場合、オーバーロード解決は引数が右辺値 (名前のない一時オブジェクトなどの prvalue または std::move の結果などの xvalue のいずれか) であればムーブ代入を選択し、引数が左辺値 (名前付きのオブジェクトまたは左辺値参照を返す関数/演算子) であればコピー代入を選択します。 コピー代入のみが提供されている場合は、すべての引数カテゴリがそれを選択します (引数を値で取るか const への参照として取る限り (右辺値は const 参照に束縛できるため))。 これは、ムーブが利用可能でないときに、コピー代入をムーブ代入のフォールバックにします。
継承階層内の複数の経路でアクセス可能な仮想基底クラスの部分オブジェクトが、暗黙に定義されたムーブ代入演算子によって複数回代入されるかどうかは、未規定です (同じことがコピー代入にも適用されます)。
ユーザ定義されたムーブ代入演算子の期待される動作のさらなる詳細については代入演算子のオーバーロードを参照してください。
[編集] 例
#include <string> #include <iostream> #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; // 暗黙のムーブ代入演算子 B& B::operator=(B&&) は // A のムーブ代入演算子を呼び、 // s2 のムーブ代入演算子を呼び、 // n のビット単位のコピーを行います。 }; struct C : B { ~C() { } // デストラクタは暗黙のムーブ代入演算子を妨げます。 }; struct D : B { D() { } ~D() { } // デストラクタは暗黙のムーブ代入演算子を妨げます。 D& operator=(D&&) = default; // ムーブ代入を強制します。 }; int main() { A a1, a2; std::cout << "Trying to move-assign A from rvalue temporary\n"; a1 = f(A()); // 右辺値一時オブジェクトからのムーブ代入。 std::cout << "Trying to move-assign A from xvalue\n"; a2 = std::move(a1); // xvalue からのムーブ代入。 std::cout << "Trying to move-assign B\n"; B b1, b2; std::cout << "Before move, b1.s = \"" << b1.s << "\"\n"; b2 = std::move(b1); // 暗黙のムーブ代入を呼びます。 std::cout << "After move, b1.s = \"" << b1.s << "\"\n"; std::cout << "Trying to move-assign C\n"; C c1, c2; c2 = std::move(c1); // コピー代入演算子を呼びます。 std::cout << "Trying 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