右辺値参照とムーブセマンティクス
資料だけならWeb上に多かれど、いまいちピンとこないのがこの2つの機能ではないかと思います。大体この2つはセットにして語られるので、どこからどこまでが右辺値参照の機能でどこからどこまでがムーブセマンティクスの機能なのかを混同してしまう、ということも理解の混乱に拍車をかけている気がします。私だけかもしれませんが。
●右辺値参照
そんなわけで明らかにしておきたい事は、つまりはこういうことです。
右辺値参照は、あくまでも「単なる参照」であり、右辺値参照=ムーブコンストラクタを呼びだすための機能、ではない
右辺値参照は、あくまでも「単なる参照」であり、Hoge &&piyo = hoge; とすればHogeのムーブコンストラクタが呼び出される、というわけではない
※ 2013/01/23:faith_and_brave様のご指摘を受けて、表現を変更いたしました。
右辺値参照も、基本的にはただの参照と何ら変わりありません。違いがあるとすると、大まかには以下の点だけです:
(1) 右辺値参照の参照先は、いつでも破壊してよい(ただし、破壊しなくてもよい
(2) 右辺値参照の参照元には、「いつでも壊してよいもの」だけを指定できる(いつでも壊してよいもの=一時変数・returnされる値、明示的にstd::move()した値)
(3) 一時変数を右辺値参照した場合、その一時変数の寿命は右辺値参照変数の寿命と一致する(一時変数へのconst参照の場合も同じ)
(4) 右辺値参照変数自体は、右辺値ではなく左辺値である(Hoge &&hoge = foo(); とした時、foo()の戻り値は右辺値だが、hoge自体は左辺値)
ここで(1)と(2)については、右辺値参照が元々そのような機能のために新設されたものですので、わかりやすいはずです。
(3)はなかなか不思議な挙動ではありますが、どうやらそういうものらしいと納得するほかないでしょう。
(4)は少し悩みますが、考えてみれば当たり前のことです。次のようなコードを書いた際にどうなるかと考えたら明らかでしょう。
Hoge &&h1 = hoge(); // 参照したので、hoge()の戻り値の寿命はh1の寿命と一致する Hoge h2(h1); // h1が右辺値だとすると、気付かないうちにHogeのムーブコンストラクタが呼ばれてh1が破壊されている可能性がある h1.piyo(); // 破壊されたオブジェクトへの不正な操作
このため、上記のようなコードではh1が左辺値として扱われ、Hogeのコピーコンストラクタが呼ばれます。ムーブコンストラクタを呼び出したい場合はstd::move()を用い、次のようにします:
Hoge &&h1 = hoge(); Hoge h2(std::move(h1)); // h1を明示的に右辺値として扱う h1.piyo(); // 依然として不正な操作ではあるが、自分でstd::move()しておきながらこんな事をやるのは明らかにプログラマーが悪い
なお、右辺値参照変数が左辺値であることは、次のような簡単なコードで確認できます。(5)と(6)が左辺値になっていることを確認して下さい。
#include <stdio.h> struct Hoge { void print(int id) & { printf("(%d) lvalue\n", id); } // オブジェクトが左辺値の場合に呼ばれる void print(int id) && { printf("(%d) rvalue\n", id); } // オブジェクトが右辺値の場合に呼ばれる } ; int main(int argc, char *argv[]) { Hoge h1 ; h1.print(1) ; // (1) lvalue Hoge &h2 = h1 ; h2.print(2) ; // (2) lvalue Hoge().print(3) ; // (3) rvalue std::move(h1).print(4) ; // (4) rvalue Hoge &&h5 = Hoge() ; h5.print(5) ; // (5) lvalue Hoge &&h6 = std::move(h1) ; h6.print(6) ; // (6) lvalue return 0; }
●ムーブセマンティクス
右辺値参照はいつでも破壊してよいことを利用して、いわゆる「破壊的コピー」を行うのが、ムーブセマンティクスです。
代表的な用途はムーブコンストラクタですが、別にコンストラクタに限る必要はありません。普通の関数の中で、引数を破壊しながら処理したっていいのです。
なお、破壊的コピー自体は右辺値参照の登場を待つまでもなく、とっくの昔にstd::auto_ptr等で使われている技術ではあります。
では一体何が新しいのかというと、「右辺値参照の登場により、破壊的コピーであることを明示できるようになった」ことです。一時変数に対して使えるようになった点も大きくはありますが、ソフトウェア開発という立場から見ると、関数シグネチャを確認するだけで破壊的かそうでないかを知ることができる方がよっぽど有益といえるでしょう(左辺値参照を利用した破壊的コピーが全く使えなくなったわけではないので、その点だけは注意ですが)。
C++11では参照を破壊してよいかどうかを簡単に見分けられるようになったため、標準ライブラリにも大幅な変更が加わっています。
具体的には、STLコンテナ等がムーブコンストラクタに対応したため、メモリ確保とコピーのオーバーヘッドが格段に減少することになりました。
他にも、左辺値参照による破壊的コピーの代表例であるstd::auto_ptrはC++11では非推奨となり、右辺値参照による破壊的コピーを行うstd::unique_ptrに取って代わられます。