2014年9月6日土曜日

move semanticsとrvalue参照

ところで、std::moveを使うだけで、どうして「移動」ができるんでしょうね?
std::string name;
name = std::move("hello");
C++98では、=は代入、つまりコピーでした。
つまり、std::moveだけでは「移動」できる訳がありません。

「移動」するための仕組みは、rvalue参照にあります。

std::moveは、実は実装としては何もしません。
この関数は、rvalue参照の引数を取り、それをrvalue参照に型変換するだけです。
この型変換によって、テンプレートの何処かでlvalueが紛れ込んでいないことを、
コンパイル時に確認します。
この型変換によって、何でも破壊可能なrvalueに変換します。
間違えてlvalue参照を渡すと破壊されるので注意が必要です。

実際の「移動」は、rvalue参照を引数にしたstd::string用の=オペレータが処理します。
=オペレータが、rvalueを参照渡しで受け取り、swapして内容を入れ替えます。
std::string&
operator=(std::string&& str)
{
    this->swap(str);
    return *this;
}
この、引数がrvalue参照の=オペレータを、
ムーブ代入オペレータ(move assignment operator)と呼びます。
これまでの引数がconst参照の=オペレータは、
これまで通り代入オペレータ(assignment operator)と呼びます。

しかし、なぜこのムーブ代入オペレータは「移動」するためのswapを使う
コードを記述できたのでしょうか?

それは、rvalue参照渡しのデータは、呼ばれ側が、
自由に壊して良いというルールが存在するからです。

そのため、上記の通りswapを使った、破壊型ですが、
メモリコピーの必要ない高速なコードを書くことができました。

C++11の実装では、std::stringやstd::vectorなど、標準ライブラリ全てに対し、
rvalue参照を取る新しいオペレータや関数を用意して、上記のようにswapを使うなどして、
効率化を向上し、高速化を達成しました。

その結果、std::moveが型変換しかしなくても、「移動」が実現されたというわけです。
これが、moveがsyntaxではなく、semanticsな理由でもあります。
素晴らしい、さあ、窓からC++98を投げ捨(ry

補足: もちろん、std::move("hello")の所も、そのままだとコピーが発生します。
そこで、std::move自体もrvalueの参照渡しで引数を受け取っています。

0 件のコメント:

コメントを投稿