C++でstd::moveを使うときに気をつけること

std::move は名前のせいで少し誤解しやすいです。

std::move(x) と書いた瞬間に何かが移動するわけではありません。x を「ムーブできる値」として扱えるようにするキャストです。

std::moveの基本

#include <string>
#include <utility>

std::string a = "hello";
std::string b = std::move(a);

この例では、std::move(a) によって a が右辺値として扱われ、std::string のムーブコンストラクタが使われます。

移動処理を実際に行うのは std::string 側です。

move後の値は使えるが中身に期待しない

ムーブ後のオブジェクトは、有効ではあります。

ただし、中身がどうなっているかには期待しない方がよいです。

std::string a = "hello";
std::string b = std::move(a);

// aは有効だが、中身は期待しない
a = "world";

再代入や破棄はできます。ムーブ前の値が残っている前提で読むのは避けます。

constにmoveしても動かないことがある

const な値に std::move しても、ムーブできないことがあります。

const std::string a = "hello";
std::string b = std::move(a);

std::move(a) の型は const std::string&& になります。

多くのムーブコンストラクタは非constの右辺値参照を受け取るため、この場合はコピーになることがあります。

const とムーブは相性がよくありません。

returnで無理にmoveしない

ローカル変数を返すときに、何でも std::move を付ける必要はありません。

std::string makeName() {
    std::string name = "hello";
    return name;
}

この形では、コピー省略やムーブが効く余地があります。

一方で、次のように書くと最適化を邪魔することがあります。

std::string makeName() {
    std::string name = "hello";
    return std::move(name);
}

最近のC++では、ローカル変数をそのまま返す形をまず考えます。

unique_ptrでは所有権を移す

std::unique_ptr はコピーできないので、別の所有者へ渡すときに std::move が必要です。

auto ptr = std::make_unique<int>(1);
auto other = std::move(ptr);

この後、ptr は空になります。

unique_ptrshared_ptr の使い分けは C++でunique_ptrとshared_ptrを使い分ける目安 に分けています。

まとめ

std::move は移動そのものではなく、ムーブできる形へ変換するものです。

  • std::move 自体はキャスト
  • 実際に移動するかは受け取る側次第
  • move後の値は有効だが中身には期待しない
  • const な値はムーブできないことがある
  • returnでは無理に std::move を付けない

使う場面を絞ると、std::move はかなり読みやすくなります。

関連記事