C++ 11 の rvalueリファレンスと swap

今までの swap

今までの swap の実装は次のような感じでしょうか

template<typename T> inline void	swap( T&	lhs, T&	rhs ){
	T temp = lhs;
	lhs = rhs;
	rhs = temp;
}

コピーが3回呼ばれます

cfoo::cfoo( const cfoo& )
cfoo::operator=( const cfoo& )
cfoo::operator=( const cfoo& )
cfoo::~cfoo

当たり前ですね
ただ、rvalueリファレンスがある今はもう当たり前ではなくなりました

rvalueリファレンスと swap

上のコードを見て最後のコピーが勿体ないということにすぐ気が付くでしょうか

コピーの後 temp はもう寿命ですね
寿命が来たオブジェクトは後は捨ててしまうだけですが、この「後は捨ててしまうだけ」という情報は今や大きな情報です
「後は捨ててしまうだけ」というのはそのコピーを move にできるという合図です

template<typename T> inline void	swap( T&	lhs, T&	rhs ){
	T	temp( lhs );
	lhs = rhs;
	rhs = std::move( temp );
}

コピーを一つ節約できました

cfoo::cfoo( const cfoo& )
cfoo::operator=( const cfoo& )
cfoo::operator=( cfoo&& )
cfoo::~cfoo

捨ててしまうものをただそのまま捨ててしまうというのは今や勿体ないことなのです

もっとmove

上記のコードは実はもっと move できます
どこでしょうか

次の行に注目してみましょう

T	temp( lhs );

ここでは temp に lhs の中身をコピーしていますね
ということはこの時点で同じ中身のものが temp と lhs と2つあることになります
コピーしているのですから当たり前のことですが、無駄ですね
しかも次の行では lhs に rhs を上書きしています

lhs = rhs;

つまりコピーを作り終わった後の lhs はもう不要なのです
「もう不要」ということはこれもこのコピーを move にできるという合図です

T	temp( std::move( lhs ));

これでもう一つコピーを節約することができました

cfoo::cfoo( cfoo&& )
cfoo::operator=( const cfoo& )
cfoo::operator=( cfoo&& )
cfoo::~cfoo

次の行はどうでしょうか

lhs = rhs;

ここも lhs に rhs の中身をコピーした時点で同じ中身のものが2つできてしまっています
しかもやはり次の行では rhs に temp を上書きしています

rhs = std::move( temp );

ということはここもやはり rhs はもう不要ですね
ここも move にできます

lhs = std::move( rhs );

つまり swap は今や次のように書けるようになったということです

template<typename T> inline void swap( T&	lhs, T&	rhs ){
	T	temp( std::move( lhs ));
	lhs = std::move( rhs );
	rhs = std::move( temp );
}

3回のコピーを全部ムーブに置き換えることができました

cfoo::cfoo( cfoo&& )
cfoo::operator=( cfoo&& )
cfoo::operator=( cfoo&& )
cfoo::~cfoo

C++ 11 時代ではこの「コピーした後にコピー元が不要になるかどうか。」という点にも気を配れるようになれるといいと思います

どのようなものが不要か

コピーした後にコピー元が不要になるケースというのは「コピー以降使われない」か「コピー後最初に使われるのが書き込み先である場合」かのどちらかの場合でしょう
書き込み先というのは式の左辺や関数の出力引数のことです

全部を上書きされることが分かっているものはもう不要でしょう
一部値が残っていることを期待されるようなものは不要ではありません
不要でないものを move してはいけません

moveした後の変数に触ってもいいのか

以前にもお話ししましたが、やはり move した後の変数に触ってもいいのかということを気にする人が結構います
もう一度言いますが move は特別な何かというわけではなくただの破壊的なコピーです
move 後に触ってはいけない理由はありません
しかしもう触らないから move するのであってまだ触りたいなら move してはいけません

ただ、ここでも lhs や rhs を move した後に代入しています
触っているのに move していいのかと思うかもしれません
触るというのは実は値を読み出す readアクセスという意味でした

ですから言い換えると、もうその変数から値を読み出さないから move するのであってまだ値を読み出したいなら move してはいけません。となるでしょうか

別に move後の変数から値を読み出したからといって例外が発生したりするようなものでもありませんが、まだ値を読み出したいならそれはまだ不要でないということです
不要でないものを move してはいけません