2012-03-22

C++11のstd::swapはC++03のstd::swapとは互換性がない

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:

  1. パフォーマンス的に以下はどうでしょうか?
    通常は、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
  2. ムーブ後のオブジェクトがすぐに破棄されるならば、結果的には全く同じ事です。
    代入演算子でswapを使うと、ムーブ後のオブジェクトは、ムーブ先で確保されたストレージを所有することになるので、
    その後普通に使うこともできます。

    swapを使うと、ストレージの破棄を必要になるまで遅らせることができるのです。

    ReplyDelete
  3. X x1(10);
    X x2(10);
    x1 = x2; // #1

    ムーブ代入演算子でstd::swap( *this, t ) ;を呼び出すと、swap内部でムーブ代入演算子がまた呼び出されるので、、#1で無限ループに落ちるのでは?

    ReplyDelete
  4. そういえばそうだ。
    やはり自前のスワップは必要かな。

    ReplyDelete

You can use some HTML elements, such as <b>, <i>, <a>, also, some characters need to be entity referenced such as <, > and & Your comment may need to be confirmed by blog author. Your comment will be published under GFDL 1.3 or later license with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts.