C++0x における NRVO

C++0x において、関数の戻り値に、関数内部のローカル変数を「そのまま」帰す場合を考えます:

std::string f()
{
  std::string s = "hoge";
  return s; 
}

この場合、戻り値として使われるローカル変数 s は、自動的に move されます。
つまり、以下のコードはコンパイルエラーになりません:

std::unique_ptr<Hoge> g()
{
  std::unique_ptr<Hoge> p( new Hoge() );
  // 変数に束縛された std::unique_ptr は、普通は move しないとダメだけど
  return p; // そのまま関数から返す場合、 std::move(p) と書かなくてもいい
}

正確に言うと、コンパイラが NRVO によってコンストラクタ呼び出しを省略していい場合*1
つまり、関数の戻り値と(CV修飾子まで)同じ型の、参照でないローカル変数 x を、そのまま return x; と返す場合には、
そのオブジェクト x は、 rvalue として扱われます。*2


もちろん、明示的に return std::move(x); と書いてもいいのですが、
明示的に move した場合、そのコードは、規格で定められた NRVO の条件を満たさなくなる為、
コードの最適化に拘りたい場合には、 move せず直接返したほうが良いかもしれません。


といっても、コピーならともかく、 move ならば NRVO で省けるコストなんて微々たるものですから、
特に気にせず return std::move(x); と返しても問題ありません。
むしろ、ローカル変数 x の型が関数の戻り値の型と異なる場合や、 x がローカル変数ではなく関数の引数*3だった場合、あるいは関数呼び出しを挟んで return f(x); と返す場合などは、明示的に move しないと move されないので、
そのような場合にうっかり move を忘れないように、常に明示的に move するようにするのも、悪くない習慣だとは思います。

*1:該当する規格: 12.8 Copying and moving class objects [class.copy] clause 32, N3225。 "When certain criteria are met" で検索すると出てきます。

*2:該当する規格: 12.8 Copying and moving class objects [class.copy] clause 33, N3225。 なお、 rvalue として扱うとコンパイルエラーになる場合には、 lvalue として扱われます。

*3:関数の引数に関しては、規格の変更によって、 NRVO が働かない場合にも自動 move されるようになったのですが、コンパイラがまだその規格に対応してないのと、 x が関数の引数の時は そもそも NRVO は働かない点を考えると、明示的に move した方がいいです。