いさぽん部屋(isapon.com)

ゲーム系プログラマによる特に方針のないブログ。技術系とカレー、ラーメンネタ多めだったはずが、最近はダイエットネタ多め。

moveされた右辺値の扱い

ふと、move後のオブジェクトの状態ってどうなんてんだっけ?と気になった。


仕様上ではムーブ後のオブジェクトは「書き換えてよい」となっているが、この場合の書き換えがどこまでの話なのかというのがちょっとわかりにくかったので、改めて勉強しなおし。


すっからかんになっているというのは解ってはいるんだけど……具体的にはどうなんだと。


コードに表すと、以下の時の a の状態はどうなんのか(というか、どう実装すべきなのか)ということ。


containor_type  a;
containor_type   b = std::move(a);

empty()やsize()は使える

下記の動画を見ると、ムーブされたオブジェクトは前提条件が必要な関数と不要な関数があり、前者は使ってはならないが後者は使えるということ。


www.youtube.com

ざっくり言うと「ステータスの確認はできるがそれ以外は保証しない」という感じだろうか?


つまり、コンテナで言うところの empty() や size() のようなは意図通りに使用できるが、push() だったり get() だったり at() だったりのような中身に触れるもの使用できない。


ムーブされた後のオブジェクトが使えるのかどうかくらいの判定はできる、もしくはできるようにしておけということっぽい。


先の例だと……

a.empty();  OK
a.size();  OK
a[i] NG
a.push_back() NG
a.find() NG

ということになる。 でも、これだと「書き換え」はできないよな……という気もしないでもない。

std::swap() ができるようにする

書き換えについての答えは std::swap にあった。STLのswapの中身は下のような感じだ。


template <typename T>
void swap(T& a, T& b)
{
    T c = std::move(a);
    a = std::move(b);
    b = std::move(c);
}

aもbも一度ムーブされているが、そのあとに operator = (T&&) で上書きされている。つまり、書き換えられているってことだ。


自分で実装する場合

基本的には std::move(my_class_obj_a, my_class_obj_b) が動くようにしておけばよい。


ムーブ後に呼ばれても良いように実装すべきものをまとめると次のような感じ。


必須となっているものは、オブジェクトがどのような使われ方をするにしても、必ず実装すべきもの。必須に近い推奨となっているものは、プライベートな型など利用法が限定的だったりわかっているものを除けば実装しておいたほうが良いもの。


関数 呼べるか 補足
デストラクタ 必須
operator = 推奨(必須に近い)
empty(), size() 推奨
clear() 推奨
operator ==, operator != 呼べる
operator ==, operator != 呼べる
initialize(), reset(), refresh() 呼べる よくある関数

あとは、次のように右辺値として連続で呼ばれても大丈夫なようにしておくくらいかな?


containor_type  a;
containor_type   b = std::move(a);
containor_type   c = std::move(a);

とりあえず迷ったときはムーブされたらデフォルトコンストラクタが呼ばれた状態まで戻しておくと良いです。