shared_ptrの実装4

前回の続き
一応スマートポインタらしく動作するshared_ptr風のクラスができましたが、何かダサいですね。美しくない。boost::shared_ptrと比較すると、全くの別物であることがわかります。参照カウンタなどの内部の実装がそのまま見えてしまっているのはよろしくないです。

実際、boost::shared_ptrの内部では、参照数を増減する部分を隠蔽しています。

    {
        shared_ptr< int > a( new int( 100 ) );
        shared_ptr< int > b = a;
        b = a;
    }

このように動作させるためのコピーコンストラクタ、operator=、デストラクタさえもboost::shared_ptrでは用意されていません。そんな馬鹿な。
というか素晴らしい。

ソースを見るとメンバは2つ。

template < typename T >
class shared_ptr
{
    ...略
private:
    T* px;
    boost::detail::shared_count pn;
};

どうやらshared_countが鍵を握っているらしい。

shared_count

shared_ptrの中の人、shared_countが本体のようですね。shared_ptrは中の人(shared_count)がポインタのように振舞うための着ぐるみです。(誰かスマポたんを擬人化してください)

class shared_count
{
private:
    sp_counted_base* pi_;  //参照カウンタの実体
};

template < typename T >
class shared_ptr
{
    ...略
private:
    T* px;
    shared_count pn;
};

shared_ptrの中の人(shared_count)が参照カウンタと参照先のT*を管理しています。参照カウンタの基底sp_counted_baseに参照カウンタlong use_count(weak_countは今回無視します)があり、その実装クラスがテンプレートでT*を管理します。

class sp_counted_base
{
private:
    long use_count;

};

template < typename T >
class sp_counted_impl
    : public sp_counted_base
{
private:
    T* ptr;
};

class shared_count
{
private:
    sp_counted_base* pi_;  //参照カウンタの実体
};

template < typename T >
class shared_ptr
{
    ...略
private:
    T* ptr;
    shared_count pn;
};

  • shared_ptrが持っているptrには所有権がありません。sp_counted_implの参照数が0になったときdeleteされます。
  • 参照カウンタの増減はshared_countのoperator=、デストラクタで行われます。
  • shared_ptrがコピーされるとき、operator=がオーバーライドされていないので、デフォルトの代入(メンバを直接コピー)が行われます。

なるほど〜。
では、実装してみましょう。

shared_ptr

まずはshared_ptr。

template < typename T >
class shared_ptr
{
private:
    T* ptr;
    shared_count count;

public:
    shared_ptr()
    : ptr( 0 )
    , count()
    {}

    explicit shared_ptr( T* ptr )
    : ptr( ptr )
    , count( ptr )
    {}

public:
    T& operator*()const
    {
        return *ptr;
    }

    T* operator->()const
    {
        return ptr;
    }
};

すっごいシンプルですね。

shared_count

次に今回のメイン。中の人。
デストラクタ、コピーコンストラクタ、operator=で参照カウンタの増減を行います。

class shared_count
{
private:
    sp_counted_base* pimpl;

public:
    shared_count()
        : pimpl( 0 )
    {}

    template < typename T >
    explicit shared_count( T* ptr )
        : pimpl( new sp_counted_impl( ptr ) )
    {}

    ~shared_count()
    {
        if( pimpl )pimpl->release();
    }

public:
    shared_count( shared_count const& sc )
        : pimpl( sc.pimpl )
    {
        if( pimpl )pimpl->addref();
    }

    shared_count& operator=( shared_count const& sc )
    {
        sp_counted_base* temp = sc.pimpl;
        if( temp != pimpl )
        {
            if( temp )temp->addref();
            if( pimpl )pimpl->release();
            pimpl = temp;
        }
        return *this;
    }
};

生ポインタを受け取るコンストラクタがテンプレートになっている部分がポイントだったりします。スマートポインタに格納する実体を生成する処理を書くまでコンパイルされない(delete ptr;がコンパイルされない)ので、不完全クラスでも格納できます。(詳細は後述)

sp_counted_base

参照カウンタの基底。shared_ptr間でこのインスタンスが共有されます。
基底では参照カウンタのみ管理。

class sp_counted_base
{
private:
    long use_count;

public:
    sp_counted_base()
        : use_count( 1 )
    {}

    virtual ~sp_counted_base()
    {}

public:
    void addref()
    {
        use_count ++;
    }

    void release()
    {
        if( -- use_count == 0 )
        {
            dispose();
            delete this;
        }
    }

public:
    virtual void dispose() = 0;
};

抽象クラスなので直接使うことはできません。実装クラスを用意し、T* ptr;をdeleteします。

sp_counted_impl

参照カウンタの実装クラス。スマートポインタに格納するインスタンスの所有権を持っています。

template < typename T >
class sp_counted_impl
	: public sp_counted_base
{
private:
    T* ptr;

public:
    sp_counted_impl( T* ptr )
        : ptr( ptr )
    {}

public:
    void dispose()
    {
        delete ptr;
    }
};


さて、うまく動作するでしょうか。。。

    {
        shared_ptr< int > a( new int( 100 ) );
        {
            shared_ptr< int > b = a;
            {
                shared_ptr< int > c;
                c = b;
                cout << *c << endl;
            }
        }
    }

一応、大丈夫そうです。。。
これでいいのか、ちょと不安。

shared_ptr< void >

を試してみました。

class Hoge
{
    int a;
public:
    ~Hoge()
    {
        cout << "~Hoge" << endl;
    }
};

int main()
{
    shared_ptr< void > a( new Hoge );
    return 0;
}

コンパイルエラー。

    T& operator*()const

「void&」は使えないらしい。(そういえば、void&ははじめて書いたかも)
では、shared_ptr_traitsを用意して、voidを特殊化しましょう。

shared_ptr_traits

template < typename T >
struct shared_ptr_traits
{
    typedef T& reference;
};
template<>
struct shared_ptr_traits< void >
{
    typedef void reference;
};

shared_ptrに追加

template < typename T >
class shared_ptr
{
public:
    typedef typename shared_ptr_traits< T >::reference;

    ...略

    reference operator*()const
    {
        return *ptr;
    }
};

コンパイルが通った!!
voidでreturn ...;と書けるのか??


しかし、当然ですがdeleteされないですね。。。
いわゆる動的削除子が必要。

  • 2006-01-08さんで詳しく書かれています。

pimplイディオム

を試してみました。

class Piyo
{
private:
    class Fuga;
    shared_ptr< Fuga > p;

public:
    Piyo();
};
class Piyo::Fuga
{
    int a;
public:
    ~Fuga()
    {
        cout << "~Fuga" << endl;
    }
};

Piyo::Piyo()
    : p( new Fuga )
{}
int main()
{
    Piyo piyo;
    return 0;
}

OKです。


まだまだ、未実装の部分が多いですが、少しずつ追加していきましょう。