C++11のstd::swapは、だいぶ大きく変更された。C++03までのswapは、<algorithm>にあり、CopyConstructibleかつAssignable(C++11のCopyAssignableに相当)を要求した。ところが、C++11では、<utility>に移されたあげく、MoveConstructibleかつMoveAssignableを要求するようになった。まるっきり別物になってしまっているのだ。果たして問題を起こさないものだろうか。おぼつかないものだ。
実装例は以下のようになる。
// C++03のswap template < typename T > void swap( T & a, T & b ) { T temp = a ; a = b ; b = temp ; } // C++11のswap template < typename T > void swap( T & a, T & b ) { T temp = std::move(a) ; a = std::move(b) ; b = std::move(temp) ; }
ところで、Andrew Koenigは、swapはコピーやムーブ以上に基本的な操作だと書いている。
Object Swapping Part 1: Its Surprising Importance | Dr Dobb's
Object Swapping, Part 2: Algorithms Can Often Swap Instead of Copy | Dr Dobb's
C++11のswapはムーブになっているので、わざわざメンバー関数としてのswapを実装する必要はない。ムーブコンストラクターさえ実装しておけば、それがそのままstd::swapで使える。さて、koenigの理論を実践してみる。
class X { private : std::size_t size ; char * ptr ; public : X( std::size_t size ) : size(size), ptr( new char[size] ) { } ~X() { delete[] ptr ; } X( X const & t ) : size( t.size ), ptr( new char[t.size] ) { std::copy( t.ptr, t.ptr + size, this->ptr ) ; } X ( X && t ) : size( t.size ), ptr( t.ptr ) { t.size = 0 ; t.ptr = nullptr ; } X & operator = ( X const & t ) { if ( this == &t ) return *this ; X temp = t ; std::swap( *this, temp ) ; return *this ; } X & operator = ( X && t ) { if ( this == &t ) return *this ; std::swap( *this, t ) ; return *this ; } } ;
なるほど、コピー/ムーブコンストラクター以外はすべてswapで実現可能なのだな。
パフォーマンス的に以下はどうでしょうか?
ReplyDelete通常は、std::swapでmoveされているため、delete [] ptr;がno-opになるので、std::swapを使うより早いと思います。
X& operator=(X&& t)
{
assert(*this != &t);
delete [] ptr; // no-op
size = t.size;
ptr = t.ptr;
t.size = 0;
t.ptr = nullptr;
return *this;
}
ムーブ後のオブジェクトがすぐに破棄されるならば、結果的には全く同じ事です。
ReplyDelete代入演算子でswapを使うと、ムーブ後のオブジェクトは、ムーブ先で確保されたストレージを所有することになるので、
その後普通に使うこともできます。
swapを使うと、ストレージの破棄を必要になるまで遅らせることができるのです。
X x1(10);
ReplyDeleteX x2(10);
x1 = x2; // #1
ムーブ代入演算子でstd::swap( *this, t ) ;を呼び出すと、swap内部でムーブ代入演算子がまた呼び出されるので、、#1で無限ループに落ちるのでは?
そういえばそうだ。
ReplyDeleteやはり自前のスワップは必要かな。