ムーブコンストラクタ
ムーブコンストラクタは、同じクラス型の引数を受け取り、その引数の内容をコピーし、場合によっては引数を変更できるコンストラクタです。
目次 |
[編集] 構文
class-name (parameter-list ); |
(1) | ||||||||
class-name (parameter-list ) function-body |
(2) | ||||||||
class-name (single-parameter-list ) = default; |
(3) | ||||||||
class-name (parameter-list ) = delete; |
(4) | ||||||||
class-name ::class-name (parameter-list ) function-body |
(5) | ||||||||
class-name ::class-name (single-parameter-list ) = default; |
(6) | ||||||||
| class-name | - | ムーブコンストラクタが宣言されているクラス |
| parameter-list | - | 以下の条件をすべて満たす空でないパラメータリスト
|
| single-parameter-list | - | 型が T&&、 const T&&、 volatile T&&、または const volatile T&& であり、デフォルト引数を持たない1つのパラメータのみのパラメータリスト |
| 関数本体 | - | ムーブコンストラクタの関数本体 |
[編集] 解説
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 };
ムーブコンストラクタは通常、オブジェクトがrvalue (xvalueまたはprvalue) (C++17まで)xvalue(C++17以降) から同じ型で初期化される場合(直接初期化またはコピー初期化によって)、次の場合に呼び出されます。
- 初期化: T a = std::move(b); または T a(std::move(b));(ここで b は型
T); - 関数引数渡し: f(std::move(a));(ここで a は型
T、 f は void f(T t)); - 関数からの戻り値: return a;(T f()のような関数内で、ここで a はムーブコンストラクタを持つ型
T).
初期化子がprvalueの場合、ムーブコンストラクタの呼び出しはしばしば最適化されます(C++17まで)行われません(C++17以降)。詳細はコピー省略を参照してください。
ムーブコンストラクタは通常、引数が保持するリソース(動的に割り当てられたオブジェクトへのポインタ、ファイルディスクリプタ、TCPソケット、スレッドハンドルなど)をコピーするのではなく転送し、引数を有効だが不定な状態のままにします。ムーブコンストラクタは引数の寿命を変更しないため、通常、デストラクタは後で引数に対して呼び出されます。例えば、std::stringやstd::vectorからのムーブは、引数を空のままにする可能性があります。一部の型、例えばstd::unique_ptrの場合、ムーブされた後の状態は完全に指定されます。
[編集] 暗黙的に宣言されるムーブコンストラクタ
クラス型に対してユーザー定義のムーブコンストラクタが提供されておらず、かつ以下の条件がすべて真である場合
- ユーザー宣言されたコピーコンストラクタがない;
- ユーザー宣言されたコピー代入演算子がない;
- ユーザー宣言されたムーブ代入演算子がない;
- ユーザー宣言されたデストラクタがない。
このとき、コンパイラは、explicitでない inline public メンバとして、シグネチャ T::T(T&&) を持つムーブコンストラクタを宣言します。
クラスは複数のムーブコンストラクタを持つことができます。例えば、T::T(const T&&) と T::T(T&&) の両方です。ユーザー定義のムーブコンストラクタが存在する場合でも、ユーザーはキーワード default を使って暗黙的に宣言されるムーブコンストラクタの生成を強制することができます。
暗黙的に宣言された(または最初の宣言でデフォルト化された)ムーブコンストラクタは、動的例外指定(C++17まで)noexcept指定(C++17以降) で記述されているような例外指定を持ちます。
[編集] 暗黙的に定義されるムーブコンストラクタ
暗黙的に宣言されたムーブコンストラクタが削除されておらず、かつトリビアルでない場合、ODR使用されるか、定数評価に必要な場合、コンパイラによって定義されます(つまり、関数本体が生成され、コンパイルされます)。共用体型の場合、暗黙的に定義されたムーブコンストラクタはオブジェクト表現を(std::memmoveのように)コピーします。非共用体クラス型の場合、ムーブコンストラクタは、オブジェクトの直接基底サブオブジェクトとメンバサブオブジェクトの完全なメンバごとのムーブを、初期化順に、xvalue引数を使用した直接初期化によって実行します。参照型である各非静的データメンバについて、ムーブコンストラクタは、ソース参照がバインドされているのと同じオブジェクトまたは関数に参照をバインドします。
これがconstexprコンストラクタ(C++23まで)constexpr関数(C++23以降)の要件を満たす場合、生成されたムーブコンストラクタはconstexprになります。
[編集] 削除されたムーブコンストラクタ
クラス T の暗黙的に宣言された、または明示的にデフォルト化されたムーブコンストラクタは、T がクラス型 M の潜在的に構築されるサブオブジェクト(またはその多次元配列)を以下の条件を満たす場合に削除として定義されます。
-
Mのデストラクタが削除されているか、コピーコンストラクタからアクセスできない場合、または Mのムーブコンストラクタを見つけるために適用されたオーバーロード解決が
- 使用可能な候補をもたらさない場合、または
- サブオブジェクトがバリアントメンバである場合、非トリビアルな関数を選択する場合。
このようなコンストラクタはオーバーロード解決によって無視されます(そうでなければ、rvalueからのコピー初期化を妨げることになります)。
[編集] トリビアルなムーブコンストラクタ
クラス T のムーブコンストラクタは、以下のすべてが真である場合にトリビアルです。
- ユーザーによって提供されていない(つまり、暗黙的に定義されているか、デフォルト化されている);
-
Tに仮想メンバ関数がない; -
Tに仮想基底クラスがない; Tのすべての直接基底に対して選択されたムーブコンストラクタがトリビアルである;Tのすべての非静的クラス型(またはクラス型の配列)メンバに対して選択されたムーブコンストラクタがトリビアルである。
トリビアルなムーブコンストラクタは、トリビアルなコピーコンストラクタと同じ動作を実行するコンストラクタであり、つまりstd::memmoveによってかのようにオブジェクト表現のコピーを作成します。C言語と互換性のあるすべてのデータ型はトリビアルに移動可能です。
[編集] 適格なムーブコンストラクタ
|
ムーブコンストラクタは、削除されていない場合に適格です。 |
(C++20まで) |
|
ムーブコンストラクタは、以下のすべての条件が満たされている場合に適格です。 |
(C++20以降) |
適格なムーブコンストラクタのトリビアル性は、クラスが暗黙的な寿命を持つ型であるか、トリビアルにコピー可能な型であるかを決定します。
[編集] 備考
強力な例外保証を可能にするため、ユーザー定義のムーブコンストラクタは例外を投げないようにすべきです。例えば、std::vectorは、要素を再配置する必要がある場合にムーブとコピーのどちらを選択するかをstd::move_if_noexceptに依存しています。
コピーコンストラクタとムーブコンストラクタの両方が提供されており、他のコンストラクタが有効でない場合、引数が同じ型のrvalue(std::moveの結果のようなxvalueまたは名前のない一時オブジェクトのようなprvalue(C++17まで))であればムーブコンストラクタが選択され、引数がlvalue(名前付きオブジェクトまたはlvalue参照を返す関数/演算子)であればコピーコンストラクタが選択されます。コピーコンストラクタのみが提供されている場合、すべての引数カテゴリがそれを選択します(rvalueは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++標準に遡って適用されました。
| DR | 適用対象 | 公開された動作 | 正しい動作 |
|---|---|---|---|
| CWG 1353 | C++11 | デフォルト化されたムーブコンストラクタが 削除として定義される条件が多次元配列型を考慮していなかった |
これらの型を考慮する |
| CWG 1402 | C++11 | 非トリビアルなコピーコンストラクタを呼び出す デフォルト化されたムーブコンストラクタが削除として定義されていた。 削除されたデフォルト化されたムーブコンストラクタが オーバーロード解決にまだ参加していた。 |
そのようなコピーを呼び出し可能にしていた。 オーバーロード解決で無視されるようにした。 オーバーロード解決で無視されるようにした |
| CWG 1491 | C++11 | rvalue参照型の非静的データメンバを持つクラスの デフォルト化されたムーブコンストラクタが削除として定義されていた。 |
この場合は削除されない。 |
| CWG 2094 | C++11 | volatileサブオブジェクトがデフォルト化されたムーブコンストラクタを 非トリビアルにしていた(CWG issue 496)。 |
トリビアル性は影響を受けない。 |
| CWG 2595 | C++20 | より制約されているがその関連する制約を満たさない別のムーブコンストラクタが 存在する場合、ムーブコンストラクタは適格ではなかった。 だがその関連する制約を満たさない |
この場合でも適格になりうる。 |