2014年9月21日日曜日

std::moveの仕組み

こないだからC++11づいてますが、std::moveの定義内容の説明でも。
template<typename _Tp>
  constexpr typename std::remove_reference<_Tp>::type&&
  move(_Tp&& __t) noexcept
  { return static_cast<typename std::remove_reference<_Tp>::type&&>(__t); }

この関数の機能は、rvalueへのキャスト、つまり破壊していいよというマークをつけることです。

やっている内容は引数を参照で受け取り(つまりコピーは発生しません)、
さらに受け取った物をrvalue参照で返す(こちらもコピーは発生しません)
ということです。

ただ、その過程で戻り値がrvalue参照になるため、コンパイラは、その値を壊していいと扱うわけです。

例えばこのstd::move関数にlvalueを渡して、
std::string hello("hello");
std::string a = std::move(hello);
std::cout << hello << std::endl;
なんてすると、何も表示されません。
lvalueの変数helloは破壊されてしまいます。
気をつけましょう。



以下、もうちょっと詳しい内容
str = std::move(std::string("hello"));
こうやってrvalueを渡すと、
constexpr std::string&&
move(std::string&& __t) noexcept
{ return static_cast<std::string&&>(__t); }
と動作します。やってることは、rvalue参照をrvalue参照にキャストして返します。
つまり、何もしません。一方、
std::string hello("hello");
str = std::move(hello);
のように上記の例と同様にlvalueを渡すと、
constexpr std::string&&
move(std::string& __t) noexcept
{ return static_cast<std::string&&>(__t); }
と動作します。その理由は、std::string& &&のように使うと、
reference collapsingが起きて、std::string&と扱われるからです。
やってることは、lvalue参照をrvalue参照にキャストして返します。
つまり、破壊していいとマークを付けている訳です。

ここで、こんなコードを書くと、どちらが呼ばれるでしょうか?
void setter1(std::string&& name)
{
  name_ = std::move(name);
}
void setter2(std::string name)
{
  name_ = std::move(name);
}
どちらの関数とも、lvalue参照のstd::moveを呼び出します。
どちらも、setterを呼び出す側から見るとrvalueを渡しているのですが、
setter関数内部からは、もう変数のアドレスが指すメモリ上の値なので、
lvalueとして扱われます。

つい、見た目でrvalueなんて勘違いしそうですが、注意しましょう(自戒)。

最後に、
void setter3(const std::string& name)
{
  name_ = std::move(name);
}
とすると、どうなるでしょう?

この場合、
constexpr const std::string&&
move(const std::string& __t) noexcept
{ return static_cast<const std::string&&>(__t); }
となります。一見rvalue参照にキャストされたように見えますが、
rvalue参照の戻り値はrvalueとして扱いますので、
const std::string&&はconst std::stringとして扱われます。
そして、const付きrvalueはlvalueですので、次の代入の=では、
move assign operatorではなく、assign operatorが呼び出されます。
結果として、const std::stringの中身は、破壊されることなくコピーされるというわけです。

色々難しいですね。
性能を上げようと、rvalue参照を使いはじめると、慣れるまで色々引っかかるかもしれません。
頑張っていきましょう。

0 件のコメント:

コメントを投稿