# ムーブコンストラクタの暗黙定義 ムーブコンストラクタを定義していないクラスでは、他になんのコンストラクタが定義されているかによって挙動が変わるらしいので、よく確認する。 ## そもそもムーブコンストラクタとは `T a = std::move(b)`や`T a(std::move(b))`のようにstd::moveを使用してオブジェクトを渡しながら初期化するときに呼ばれるコンストラクタのこと。 他にも`f(std::move(a))`のような関数への引数渡しのときや、関数の返り値で値を返すときにもムーブコンストラクタが使用されている。 以下のように定義することができる。 ```cpp= class MoveCtor { MoveCtor(MoveCtor&& m) : str_(std::move(m.str_)) { std::cout << "move ctor" << std::endl; } }; ``` これに似た概念としてコピーコンストラクタがある。 コピーコンストラクタは、ムーブではなくオブジェクトをコピーして渡す。 ```cpp= class MoveCtor { MoveCtor(const MoveCtor& m) : str_(m.str_) { std::cout << "copy ctor" << std::endl; } } ``` moveしてあげることによりコピーを行わずに効率的に値を受け渡すことができる。 なおmoveしたあとの元の変数は謎挙動をする。 ただし、所有権の移動のための実装では、ムーブ後はいい感じの無効値になる。 unique_ptrの例ではstd::move後はnullptrが入る。 unique_ptrは所有者を一人に限定するが所有権の受け渡しは許されているクラスなので、内部実装はコピーコンストラクタが消されていてムーブコンストラクタが定義されている。 ## 定義されていないときの挙動 普通のコンストラクタやデストラクタの他にもコピー・ムーブのコンストラクタなどがあるが、これらを毎回定義すると面倒くさい。 ムーブが定義されている場合は当然それが使われるが、定義しないときどういう挙動をするのか? 何も宣言されていないとき、defaultにフォールバックする defaultでは、各メンバ変数及び継承元クラスの対応するコンストラクタが呼ばれることになる。 コンパイラは`T::T(T&&)`のシグネチャを持つムーブコンストラクタのメンバ関数をinline publicとして定義してくれる。 しかしcopy constructor, copy assignment operators, move assigmnment operator またはdestructorのうちいずれかでも定義されていた場合、[ムーブコンストラクタの暗黙定義はできない](https://en.cppreference.com/w/cpp/language/move_constructor#Implicitly-declared_move_constructor)。つまりdefaultが暗黙にセットされない。 その場合、コピーコンストラクタにフォールバックする。 一方でコピーコンストラクタは定義されていない場合[常にdefaultがinline publicで定義される](https://en.cppreference.com/w/cpp/language/copy_constructor#Implicitly-declared_copy_constructor)。 デストラクタだけあって何も宣言されていないときはstd::moveをやるとデフォルトのコピーが使われる。 ## 実際にテストしてみた 以下が実際のテストコード。 ```cpp= #include <iostream> #include <string> #include <utility> class MoveCtor { public: MoveCtor() : str_("hello") { std::cout << "MoveCtor!" << std::endl; } MoveCtor(const MoveCtor& m) : str_(m.str_) { std::cout << "copy ctor" << std::endl; } MoveCtor(MoveCtor&& m) : str_(std::move(m.str_)) { std::cout << "move ctor" << std::endl; } private: std::string str_; }; class NoCtor : public MoveCtor { public: NoCtor() { std::cout << "NoCtor!" << std::endl; } }; class Dtor : public MoveCtor { public: Dtor() { std::cout << "Dtor" << std::endl; } ~Dtor() { std::cout << "Dtor's dtor" << std::endl; } }; class DeleteCopy : public MoveCtor { public: DeleteCopy() { std::cout << "DeleteCopy" << std::endl; } DeleteCopy(const DeleteCopy&) = delete; }; class ExplicitMove : public MoveCtor { public: ExplicitMove() { std::cout << "ExplicitMove!" << std::endl; } ~ExplicitMove() { std::cout << "ExplicitMove's dtor" << std::endl; } ExplicitMove(ExplicitMove&&) = default; }; int main(void) { auto m1 = MoveCtor(); std::cout << "-----------------------" << std::endl; auto m2 = std::move(m1); std::cout << "-----------------------" << std::endl; auto n1 = NoCtor(); std::cout << "-----------------------" << std::endl; auto n2 = std::move(n1); std::cout << "-----------------------" << std::endl; auto d1 = Dtor(); std::cout << "-----------------------" << std::endl; auto d2 = std::move(d1); std::cout << "-----------------------" << std::endl; /* auto dc1 = DeleteCopy(); std::cout << "-----------------------" << std::endl; auto dc2 = std::move(dc1); std::cout << "-----------------------" << std::endl; */ auto e1 = ExplicitMove(); std::cout << "-----------------------" << std::endl; auto e2 = std::move(e1); std::cout << "-----------------------" << std::endl; return 0; } ``` 出力結果は以下 ```cpp= MoveCtor! ----------------------- move ctor ----------------------- MoveCtor! NoCtor! ----------------------- move ctor ----------------------- MoveCtor! Dtor ----------------------- copy ctor ----------------------- MoveCtor! ExplicitMove! ----------------------- move ctor ----------------------- ExplicitMove's dtor ExplicitMove's dtor Dtor's dtor Dtor's dtor ``` 見ての通り、デストラクタを宣言しているDtorではstd::moveをしているにも関わらずMoveCtorで定義したコピーコンストラクタが呼ばれている。 一方ExplicitMoveでは明示的にdefaultムーブコンストラクタを指定しているので、親のMoveCtorもムーブされていることが確認できる。 またDeleteCopyのようにコピーコンストラクタを明示的にdeleteしている場合、デフォルトムーブコンストラクタにfallbackしたりといったことはなく、コピーと同じ挙動をするというルールに則ってstd::moveもコンパイルが通らなくなる。 ## move assignが登場する変な例 ところで以下の例を見てみよう。 ```cpp= class MoveAssign : public MoveCtor { public: MoveAssign() { std::cout << "MoveAssign!" << std::endl; } MoveAssign& operator=(MoveAssign&& m) { std::cout << "move assign op" << std::endl; return *this; } }; int main(void) { auto ma1 = MoveAssign(); std::cout << "-----------------------" << std::endl; auto ma2 = std::move(ma1); std::cout << "-----------------------" << std::endl; return 0; } ``` 最後のMoveAssignは以下のエラーでコンパイルが通らない > note: ‘MoveAssign::MoveAssign(const MoveAssign&)’ is implicitly declared as deleted because ‘MoveAssign’ declares a move constructor or move assignment operator Move assignが宣言されているので上記のルールではimplicitに宣言されないところまではわかる。 その結果同様に`MoveAssign::MoveAssign(const MoveAssign&)`すなわちコピーコンストラクタにフォールバックしていることがエラーメッセージからわかる。 で、そのコピーコンストラクタはmove assignment operatorが宣言されていることによりコピーは暗黙的にdeleteされているとのことらしい。 実際[cpp reference](https://en.cppreference.com/w/cpp/language/copy_constructor#Deleted_copy_constructor)にもそう書いてあった > The implicitly-declared copy constructor for class T is defined as deleted if T declares a move constructor or move assignment operator.