Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
38
Help us understand the problem. What are the problem?

More than 5 years have passed since last update.

@termoshtt

自作クラスをムーブする

ユーザー定義のクラスにmove constructorを追加するための方法についてまとめます。
既存の型にstd::move使うと便利なので、自作の型に対しても定義したい、という方向けです。

先に結論をまとめておくと、注意するべきは以下の3点です:

  • ユーザー定義のコピーコンストラクタやデストラクタがあるとデフォルトのムーブコンストラクタは作られない
  • ムーブされた残り滓a = std::move(b)bのデストラクタは呼ばれる
  • noexcept付けないとムーブの恩恵が得られなくなる場合がある

ムーブコンストラクタはいつ勝手に作られるの?

unique_ptrvectorの様な既存の型を複数個組合せた構造体

struct MyStruct{
  std::unique_ptr<MyStruct> p;
  std::vector<int> v;
};

において期待されるムーブコンストラクタは、
個々の要素p, vのムーブコンストラクタをそれぞれ呼び出すようなものでしょう。
以下の5つの条件を満す場合、デフォルトのムーブコンストラクタがimplicitに定義されます。

  • ユーザー定義コピーコンストラクタが無い
  • ユーザー定義コピー代入演算子が無い
  • ユーザー定義ムーブ代入演算子が無い
  • ユーザー定義デストラクタがない
  • (until C++14) 以下に説明する条件によってムーブコンストラクタがdeleted指定になっていない

ただしT(T&&) = default;で強制的にデフォルトのムーブコンストラクタを定義させる事ができます。

struct A{
  A(){}
  ~A(){}             // 自作デストラクタがあるので
                     // デフォルトのムーブコンストラクタの生成が阻害される
  A(A&&) = default;  // これでデフォルトのムーブコンストラクタが定義される
};

以下の場合にはデフォルトのムーブコンストラクタがdeleted指定されます

  • ムーブできない非静的データメンバーがある
  • ムーブできないクラスのインスタンスを含んでいる
  • デストラクタが無いかアクセスできないクラスのインスタンスを含んでいる
  • 共用体であり、非自明なコピーコンストラクタを持つvariant memberを持つ場合
  • (until C++14) ムーブコンストラクタが無く、自明なコピーができない(not trivially copyable)非静的メンバーがある

参考HP

ムーブされた残骸に対しても呼ばれるデストラクタ

ムーブされた残り滓a = std::move(b)bのデストラクタは呼ばれる:

#include <iostream>

class A {
public:
  A() { std::cout << "Constructor " << std::endl; }
  A(const A &a) { std::cout << "Copy Constructor " << std::endl; }
  A(A &&a) noexcept { std::cout << "Move Constructor " << std::endl; }
  ~A() { std::cout << "Destructor " << std::endl; }
};

A create_A() {
  A tmp;                 // call constructor
  return std::move(tmp); // call move constructor
                         // call destructor
}

int main() {
  A a = create_A();
  return 0;
  // call destructor
}

この出力は以下のようにデストラクタが2回呼ばれている事に注意する:

Constructor 
Move Constructor 
Destructor 
Destructor 

付けられる時はnoexceptを必ず付けよう

ムーブコンストラクタがnoexceptであるかを判定しているアルゴリズムがあります。
例えばstd::vectorpush_backではGCC4.7以降でstd::move_if_noexceptを使用するため、
noexceptのついていないムーブコンストラクタは使われません。
自作クラスのムーブコンストラクタを書く際は注意しましょう。

参考HP

move assignment operator (移動代入演算子)

効率的な実装の方法として以下の方法が考えられます:

  1. 先にmove assignment operator operator=(T&&) を定義する
  2. move constructorを T(T&& other) noexcept { *this = std::move(other); } で定義する

こうすれば同じ処理を重複なく書けます。
こちらにもnoexceptをつける事を忘れずに。また自己代入に注意しましょう。

T& operator=(T&& other) noexcept{
  if(this != &other){
    // moveの処理
  }
  return *this;
}

参考HP

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
38
Help us understand the problem. What are the problem?