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で実現可能なのだな。
4 comments:
パフォーマンス的に以下はどうでしょうか?
通常は、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;
}
ムーブ後のオブジェクトがすぐに破棄されるならば、結果的には全く同じ事です。
代入演算子でswapを使うと、ムーブ後のオブジェクトは、ムーブ先で確保されたストレージを所有することになるので、
その後普通に使うこともできます。
swapを使うと、ストレージの破棄を必要になるまで遅らせることができるのです。
X x1(10);
X x2(10);
x1 = x2; // #1
ムーブ代入演算子でstd::swap( *this, t ) ;を呼び出すと、swap内部でムーブ代入演算子がまた呼び出されるので、、#1で無限ループに落ちるのでは?
そういえばそうだ。
やはり自前のスワップは必要かな。
Post a Comment