CPPの右辺値参照・ムーブセマンティクス

この内容についてはすでに多くの記事がネット上にあるけど、自分の理解を深めるために(役に立つかわからない)記事を投稿することにした。詳細は他の記事に任せて、ここでは例をあげるだけにする。

右辺値参照やムーブセマンティクスを使わないで書くと、

#include <iostream>

struct str
{
    // normal constructor
    str() { std::cout << "constructor: " << this << std::endl; }
    // copy constructor
    str(const str & s) { std::cout << "copy constructor: " << this << "<-" << &s << std::endl; }
    // normal substitution
    str & operator = (str & s) { std::cout << "copy: " << this << "<-" << &s <<  std::endl; return (*this); }

    ~str() { std::cout << "destructor: " << this << std::endl; }
};

str hoge(str s) { std::cout << &s << std::endl; return s; }

という状況で、

str s = hoge(str());
std::cout << "program end: " << &s << std::endl;
constructor: 0x7fff5598e860
0x7fff5598e860
copy constructor: 0x7fff5598e868<-0x7fff5598e860
destructor: 0x7fff5598e860
program end: 0x7fff5598e868
destructor: 0x7fff5598e868
str t = str();
str u = hoge(t);
std::cout << "program end: " << &t << ":" << &u << std::endl;
constructor: 0x7fff5598e850
copy constructor: 0x7fff5598e840<-0x7fff5598e850
0x7fff5598e840
copy constructor: 0x7fff5598e848<-0x7fff5598e840
destructor: 0x7fff5598e840
program end: 0x7fff5598e850:0x7fff5598e848
destructor: 0x7fff5598e848
destructor: 0x7fff5598e850
str s = str();
str t = str();
s = t;
constructor: 0x7fff5c47a8a8
constructor: 0x7fff5c47a8a0
copy: 0x7fff5c47a8a8<-0x7fff5c47a8a0
destructor: 0x7fff5c47a8a0
destructor: 0x7fff5c47a8a8

となる。そして、

str s = str();
s = str();
error: no viable overloaded '='
    s = hoge(s)
    ~ ^ ~~~~~~~
note: candidate function not viable: expects an l-value
      for 1st argument
    str & operator = (str & s) { std::cout << "copy: " << this << "<-...

となる。オーバーロードした代入演算子は、右辺値を右辺には取れないのだ。ちなみに、オーバーロードしなければエラーは出ない。デフォルトではうまく処理されているのだろう。一方、右辺値参照やムーブセマンティクスを使うと、

#include <iostream>

struct str
{
    // normal constructor
    str() { std::cout << "constructor: " << this << std::endl; }
    // copy constructor
    str(const str & s) { std::cout << "copy constructor: " << this << "<-" << &s << std::endl; }
    // move constructor
    str(str && s) { std::cout << "move constructor: " << this << "<-" << &s << std::endl; }
    // normal substitution
    str & operator = (str & s) { std::cout << "copy: " << this << "<-" << &s <<  std::endl; return (*this); }
    // move substitution
    str & operator = (str && s) { std::cout << "move copy: " << this << "<-" << &s << std::endl; return (*this); }

    ~str() { std::cout << "destructor: " << this << std::endl; }
};

str hoge(str s) { std::cout << &s << std::endl; return s; }

という状況で、

str s = hoge(str());
std::cout << "program end: " << &s << std::endl;
constructor: 0x7fff534bc838
0x7fff534bc838
move constructor: 0x7fff534bc840<-0x7fff534bc838
destructor: 0x7fff534bc838
program end: 0x7fff534bc840
destructor: 0x7fff534bc840
str t = str();
str u = hoge(t);
std::cout << "program end: " << &t << ":" << &u << std::endl;
constructor: 0x7fff534bc828
copy constructor: 0x7fff534bc818<-0x7fff534bc828
0x7fff534bc818
move constructor: 0x7fff534bc820<-0x7fff534bc818
destructor: 0x7fff534bc818
program end: 0x7fff534bc828:0x7fff534bc820
destructor: 0x7fff534bc820
destructor: 0x7fff534bc828
str v = str();
str w = hoge(std::move(v));
std::cout << "program end: " << &v << ":" << &w << std::endl;
constructor: 0x7fff534bc810
move constructor: 0x7fff534bc800<-0x7fff534bc810
0x7fff534bc800
move constructor: 0x7fff534bc808<-0x7fff534bc800
destructor: 0x7fff534bc800
program end: 0x7fff534bc810:0x7fff534bc808
destructor: 0x7fff534bc808
destructor: 0x7fff534bc810
str s = str();
s = str();
constructor: 0x7fff5ea0f8a8
constructor: 0x7fff5ea0f8a0
move copy: 0x7fff5ea0f8a8<-0x7fff5ea0f8a0
destructor: 0x7fff5ea0f8a0
destructor: 0x7fff5ea0f8a8

という感じになる。ムーブコンストラクタ等の実装にもよるが、処理が重いコピーをせずに済んだりするから、プログラムの実行が早くなるらしい。