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