c++boost.gif (8819 bytes) スマートポインタプログラミングテクニック

不完全クラスを利用して実装を隠蔽する
"Pimpl" イディオム
抽象クラスを利用して実装を隠蔽する
delete px.get() を予防する
shared_ptr を利用して配列へのポインタを保持する
オブジェクトの確保の詳細をカプセル化し、ファクトリ関数をラップする
静的なオブジェクトを指すポインタを shared_ptr で保持する
shared_ptr を利用して COM オブジェクトへのポインタを保持する
shared_ptr を利用して組み込み参照カウントを持つオブジェクトを保持する
shared_ptr を利用して他の所有権共有スマートポインタを保持する
生ポインタから shared_ptr を獲得する
コンストラクタの中で this から shared_ptr (weak_ptr) を獲得する
this から shared_ptr を獲得する
shared_ptr を参照カウント付きハンドルとして利用する
shared_ptr を利用して、スコープを抜ける際にコードを実行する
shared_ptr<void> を利用して任意のオブジェクトを保持する
異なる種類の shared_ptr インスタンスで任意のデータを結びつける
shared_ptr をコピーコンストラクト可能(CopyConstructible)なミューテックスロックとして利用する
shared_ptr を利用してメンバ関数の呼び出しをラップする
デアロケーションの遅延
shared_ptr に管理されていないオブジェクトを指す弱参照ポインタ

不完全クラスを利用して実装を隠蔽する

既に確立されているテクニック(C言語でも動作する)のひとつに、 不完全クラスへのポインタを不明瞭なハンドルのように用いて、 インターフェースと実装を分離するというものがある:

class FILE;

FILE * fopen(char const * name, char const * mode);
void fread(FILE * f, void * data, size_t size);
void fclose(FILE * f);

shared_ptr を利用することで、 上記のインターフェースを明示的に fclose を呼び出す必要が無いような形に表現することができる:

class FILE;

shared_ptr<FILE> fopen(char const * name, char const * mode);
void fread(shared_ptr<FILE> f, void * data, size_t size);

このテクニックは shared_ptr が任意のデリータ(deleter)を実行できるという能力によって明示的な fclose の呼び出しを取り除くことができ、 また X が不完全クラスであっても、 shared_ptr<X> のコピーが可能となる。

"Pimpl" イディオム

C++では、不完全クラスのパターンの中でも特に "Pimpl" イディオムというものがある。 不完全クラスをユーザーに開示せず、 ファサードの裏に隠蔽するというものである。 shared_ptr は、このような "Pimpl" を実装するのに利用できる。

// file.hpp:

class file
{
private:

    class impl;
    shared_ptr<impl> pimpl_;

public:

    file(char const * name, char const * mode);

    // compiler generated members are fine and useful

    void read(void * data, size_t size);
};
// file.cpp:

#include "file.hpp"

class file::impl
{
private:

    impl(impl const &);
    impl & operator=(impl const &);

    // private data

public:

    impl(char const * name, char const * mode) { ... }
    ~impl() { ... }
    void read(void * data, size_t size) { ... }
};

file::file(char const * name, char const * mode): pimpl_(new impl(name, mode))
{
}

void file::read(void * data, size_t size)
{
    pimpl_->read(data, size);
}

ここでポイントとなるのは、コンパイラが生成するコピーコンストラクタや代入演算子、 デストラクタが思いのほか大きな意味を持つことである。 結果的に、file は コピーコンストラクト可能可能( CopyConstructible ) かつ 代入可能( Assignable ) になり、標準のコンテナに入れることができる。

抽象クラスを利用して実装を隠蔽する

C++ で広く利用されているもう一つの実装を分離させるイディオムとして、 抽象基底クラスとファクトリ関数を利用して実装を隠すというものがある。 抽象クラスはまた "インターフェース" とも呼ばれ、このパターンは "インターフェース指向プログラミング" として知られる。 ここでも、ファクトリ関数が返す型として shared_ptr を利用することができる:

// X.hpp:

class X
{
public:

    virtual void f() = 0;
    virtual void g() = 0;

protected:

    ~X() {}
};

shared_ptr<X> createX();
-- X.cpp:

class X_impl: public X
{
private:

    X_impl(X_impl const &);
    X_impl & operator=(X_impl const &);

public:

    virtual void f()
    {
      // ...
    }

    virtual void g()
    {
      // ...
    }
};

shared_ptr<X> createX()
{
    shared_ptr<X> px(new X_impl);
    return px;
}

shared_ptr のキーとなる特性は、確保、構築、解放、破棄の詳細を構築の時点で捕捉できる事である。 つまり、それらの各要素をファクトリ関数の中で決定してしまうことができるのである。 上に示したサンプルのコードのデストラクタが、 プロテクトかつ非仮想(nonvirtual)であることに注意しよう。 クライアントコードは X を指すポインタを削除することができないし、削除する必要もない; createX が返す shared_ptr<X> のインスタンスが、 正しく ~X_impl を呼び出すためである。

delete px.get() を予防する

しばしば shared_ptr により管理されているポインタをクライアントのコードで削除されるのを防止したい時がある。 これを実現できる一つのテクニックは、前述のプロテクトのデストラクタを用いるものである。 もう一つのテクニックは、プライベートな削除子(deleter)を用いるものである:

class X
{
private:

    ~X();

    class deleter;
    friend class deleter;

    class deleter
    {
    public:

        void operator()(X * p) { delete p; }
    };

public:

    static shared_ptr<X> create()
    {
        shared_ptr<X> px(new X, X::deleter());
        return px;
    }
};

shared_ptr を利用して配列へのポインタを保持する

shared_ptrnew[] で確保された配列へのポインタを保持するために利用することができる:

shared_ptr<X> px(new X[1], checked_array_deleter<X>());

付け加えておくと、上記のテクニックは飽くまでも一つのオプションであり、 配列のポインタを保持するには shared_array を利用する方が好ましい。 shared_array が配列に特化したインターフェースを備えているためである; shared_array には operator*operator-> が無く、ポインタの変換も許可しないようになっている。

オブジェクトの確保の詳細をカプセル化し、ファクトリ関数をラップする

オブジェクトの確保の詳細をカプセル化するために、 ファクトリ関数にて生ポインタを返すような既存の C 言語スタイルのライブラリインターフェースがあるとする。 このインターフェースをラップする C++ のラッパを作成する際に、shared_ptr を利用できる。 例えば、X を自身のプライベートなヒープに確保する CreateX というインターフェースがあるとすると、~X がアクセスできないか、X が不完全型であるかもしれない:

X * CreateX();
void DestroyX(X *);

CreateX が返したポインタを確実に破棄するためには、 DestroyX を呼ぶしかない。

ここに shared_ptr を元にしたラッパがどのようになるかを示す:

shared_ptr<X> createX()
{
    shared_ptr<X> px(CreateX(), DestroyX);
    return px;
}

createX を呼び出すクライアントコードもオブジェクトがどのように確保されたかを知る必要はないし、 更に shared_ptr の機能により、オブジェクトの破棄も自動化される。

静的なオブジェクトを指すポインタを shared_ptr で保持する

時おり、既に存在しているオブジェクトを指す shared_ptr を作りたい時がある。 その場合、 shared_ptr にそのオブジェクトを削除させないようにしなくてはならない。 例えば、次のファクトリ関数で:

shared_ptr<X> createX();

状況によっては静的に確保された X のインスタンスを指すポインタを返す必要がある。

この解決法は、"何もしない" カスタム削除子(deleter)を利用することである:

struct null_deleter
{
    void operator()(void const *) const
    {
    }
};

static X x;

shared_ptr<X> createX()
{
    shared_ptr<X> px(&x, null_deleter());
    return px;
}

ポインタよりも寿命の長いオブジェクトをスマートポインタに格納したいときも、同様のテクニックを利用できる。

shared_ptr を利用して COM オブジェクトへのポインタを保持する

背景: COM オブジェクトは組み込み参照カウントと、 その参照カウントを操作するための 2 つのメンバ関数を持つ。 AddRef() は参照カウントを増加させる。 Release() は参照カウントを減少させ、もし参照カウントが 0 になった場合は、自身を破棄する。

COM オブジェクトを指すポインタを shared_ptr に入れて保持することができる:

shared_ptr<IWhatever> make_shared_from_COM(IWhatever * p)
{
    p->AddRef();
    shared_ptr<IWhatever> pw(p, mem_fn(&IWhatever::Release));
    return pw;
}

ただし、上記の pw から作成された shared_ptr のコピーによる参照の増加は、COM オブジェクトの組み込み参照カウントには "反映されない" ことに注意せよ; pw からコピーされた shared_ptr オブジェクトは、 make_shared_from_COM によって作成された一つの参照を共有する。 pw から作成された弱参照ポインタ(Weak pointers)は、COM オブジェクトが依然生存しているかどうかに関わらず、最後の shared_ptr が破棄された時点で無効になる。

shared_ptr を利用して組み込み参照カウントを持つオブジェクトを保持する

このテクニックは、上記のテクニックを一般化したものである。 例として、オブジェクトが intrusive_ptr必要とされる 2 つの関数 intrusive_ptr_add_refintrusive_ptr_release を実装していると仮定する:

template<class T> struct intrusive_deleter
{
    void operator()(T * p)
    {
        if(p) intrusive_ptr_release(p);
    }
};

shared_ptr<X> make_shared_from_intrusive(X * p)
{
    if(p) intrusive_ptr_add_ref(p);
    shared_ptr<X> px(p, intrusive_deleter<X>());
    return px;
}

shared_ptr を利用して他の所有権共有スマートポインタを保持する

shared_ptr の設計における 1 つのゴールは、 ライブラリのインターフェースとして利用されることである。 そうなると、shared_ptr を引数に取るものの、 内部のオブジェクトは別の参照カウント方式や連結リスト方式のスマートポインタで 管理されているという状況に遭遇する可能性がある。

shared_ptr のカスタム削除子(deleter)の機能を利用することで、 これらの既存のスマートポインタを shared_ptr ファサードの裏に隠蔽し、ラップすることができる:

template<class P> struct smart_pointer_deleter
{
private:

    P p_;

public:

    smart_pointer_deleter(P const & p): p_(p)
    {
    }

    void operator()(void const *)
    {
        p_.reset();
    } 
    
    P const & get() const
    {
        return p_;
    }
};

shared_ptr<X> make_shared_from_another(another_ptr<X> qx)
{
    shared_ptr<X> px(qx.get(), smart_pointer_deleter< another_ptr<X> >(qx));
    return px;
}

些細なことだが、削除子(deleter)は例外を送出してはならない事になっており、 上記の例は p_.reset() が例外を送出しないという仮定の下に書かれている。 もしそうでない場合は、例外を黙殺するために p_.reset()try {} catch(...) {} ブロックでラップすべきである。 ここで(滅多に起こることではないが)例外が送出されそれが黙殺された場合、 p_ は削除子(deleter)の寿命が尽きたときに解放される。 これは、弱参照ポインタ(weak pointer)を含む全ての参照が破棄またはリセットされる時に起こる。

更に工夫することで、上記の shared_ptr のインスタンスから元のスマートポインタを復元することもできる。 それには get_deleter を利用する:

void extract_another_from_shared(shared_ptr<X> px)
{
    typedef smart_pointer_deleter< another_ptr<X> > deleter;

    if(deleter const * pd = get_deleter<deleter>(px))
    {
        another_ptr<X> qx = pd->get();
    }
    else
    {
        // not one of ours
    }
}

生ポインタから shared_ptr を獲得する

時には、既に他の shared_ptr インスタンスにより管理されているオブジェクトを指す生ポインタから shared_ptr を獲得する必要がある事もある。 例えば:

void f(X * p)
{
    shared_ptr<X> px(???);
}

f の中で、*p から shared_ptr を作成したい。

一般的なケースでは、この問題の解決法はない。 もし可能であれば、引数を shared_ptr で取るように f を修正する:

void f(shared_ptr<X> px);

非バーチャルなメンバ関数にて潜在的に渡される this ポインタにもにも同様の変形が利用できる:

void X::f(int m);

これは、shared_ptr を第一引数にとる非メンバ関数で置きかえられる:

void f(shared_ptr<X> this_, int m);

もし f を変更することができない場合でも、X が埋め込みカウントを利用していれば、上で説明されている make_shared_from_intrusive を利用できる。 または、f の中で作成された shared_ptr が、そのオブジェクトよりも長く生存することがないと分かっている場合は、 a null deleter を利用できる。

コンストラクタの中で this から shared_ptr (weak_ptr) を獲得する

設計によっては、オブジェクトは構築時に自身を管理機構に登録する必要がある。 登録ルーチンが shared_ptr を引数に取る場合、 どのようにして this から shared_ptr を獲得するのかという疑問が生じる:

class X
{
public:

    X()
    {
        shared_ptr<X> this_(???);
    }
};

一般的なケースでは、この問題は解決できない。 X のインスタンスが自動変数または静的変数として構築されることがある; オブジェクトはヒープ上に作成される:

shared_ptr<X> px(new X);

しかし、構築時には px は依然存在しておらず、 この shared_ptr と所有権を共有する shared_ptr インスタンスを作ることも不可能である。

文脈によっては、もし内部の shared_ptr this_ がオブジェクトの生存期間を管理する必要がない場合は、 ここここ で説明されている null_deleter を利用できる。 もし X が常にヒープ上に存在すると仮定されており、 shared_ptr によって管理される場合は、 静的なファクトリ関数を利用する:

class X
{
private:

    X() { ... }

public:

    static shared_ptr<X> create()
    {
        shared_ptr<X> px(new X);
        // use px as 'this_'
        return px;
    }
};

this から shared_ptr を獲得する

時には、this が既に shared_ptr に管理されているという仮定の下、仮想メンバ関数の中で this から shared_ptr を獲得する必要がある事がある。 その場合、上で説明したテクニック の変形が適用できない。

典型的な例:

class X
{
public:

    virtual void f() = 0;

protected:

    ~X() {}
};

class Y
{
public:

    virtual shared_ptr<X> getX() = 0;

protected:

    ~Y() {}
};

// --

class impl: public X, public Y
{
public:

    impl() { ... }

    virtual void f() { ... }

    virtual shared_ptr<X> getX()
    {
        shared_ptr<X> px(???);
        return px;
    }
};

この解決法は、impl のメンバに this への弱参照ポインタを持たせることである:

class impl: public X, public Y
{
private:

    weak_ptr<impl> weak_this;

    impl(impl const &);
    impl & operator=(impl const &);

    impl() { ... }

public:

    static shared_ptr<impl> create()
    {
        shared_ptr<impl> pi(new impl);
        pi->weak_this = pi;
        return pi;
    }

    virtual void f() { ... }

    virtual shared_ptr<X> getX()
    {
        shared_ptr<X> px(weak_this);
        return px;
    }
};

上記の解決法をカプセル化するためのヘルパクラステンプレート enable_shared_from_this が、このスマートポインタライブラリに追加された:

class impl: public X, public Y, public enable_shared_from_this<impl>
{
public:

    impl(impl const &);
    impl & operator=(impl const &);

public:

    virtual void f() { ... }

    virtual shared_ptr<X> getX()
    {
        return shared_from_this();
    }
}

shared_ptr を参照カウント付きハンドルとして利用する

幾つかのライブラリのインターフェースは 前述の 不完全クラスのテクニック で説明されている類の、不透明なハンドルを利用している:

typedef void * HANDLE;
HANDLE CreateProcess();
void CloseHandle(HANDLE);

生ポインタの代わりに shared_ptr をハンドルのように利用することで、 参照カウント及びリソース管理の自動化ができる:

typedef shared_ptr<void> handle;

handle createProcess()
{
    shared_ptr<void> pv(CreateProcess(), CloseHandle);
    return pv;
}

shared_ptr を利用して、スコープを抜ける際にコードを実行する

コントロールがスコープを外れる際に shared_ptr<void> は自動的に後処理のコードを実行できる。

    shared_ptr<void> guard(p, f);
    shared_ptr<void> guard(static_cast<void*>(0), bind(f, x, y));

さらに完全な方法については、 Andrei Alexandrescu と Petru Marginean による論文 "Simplify Your Exception-Safe Code" (例外安全の為のコードを簡素化する)を参照のこと。 この論文はオンラインで入手できる http://www.cuj.com/experts/1812/alexandr.htm?topic=experts

shared_ptr<void> を利用して任意のオブジェクトを保持する

shared_ptr<void>void* のように一般的なオブジェクトのポインタとして振る舞うことができる。 shared_ptr<void> のインスタンスが次のように構築されるとき:

    shared_ptr<void> pv(new X);

この shared_ptr インスタンスが破棄されるとき、 X のオブジェクトは ~X の実行により適切に破棄される。

この様な使い方をするときは、 オブジェクトを指すポインタから一時的に方情報を取り除くために void* を使うときと同様の流儀に従う必要がある。 shared_ptr<void>static_pointer_cast を利用して元の型に戻す(cast back)する事ができる。

異なる種類の shared_ptr インスタンスで任意のデータを結びつける

shared_ptrweak_ptrstd::map のような標準の連想コンテナで要求される比較演算子 operator< をサポートしている。 これにより、内部的に関連のない任意のデータを shared_ptr に管理されるオブジェクトと結びつけられる。

typedef int Data;

std::map< shared_ptr<void>, Data > userData;
// or std::map< weak_ptr<void>, Data > userData; 生存期間に影響を及ぼさない場合

shared_ptr<X> px(new X);
shared_ptr<int> pi(new int(3));

userData[px] = 42;
userData[pi] = 91;

shared_ptr をコピーコンストラクト可能(CopyConstructible)なミューテックスロックとして利用する

時には関数からミューテックスロックを返す必要のある時があるが、 コピー不可なロックオブジェクトは値渡しで返すことができない。 shared_ptr をミューテックスロックとして利用するとその制限を解除できる:

class mutex
{
public:

    void lock();
    void unlock();
};

shared_ptr<mutex> lock(mutex & m)
{
    m.lock();
    return shared_ptr<mutex>(&m, mem_fn(&mutex::unlock));
}

状況は改善されたが、さらに専用の shared_lock クラスによりロックオブジェクトとしての動作をカプセル化できる:

class shared_lock
{
private:

    shared_ptr<void> pv;

public:

    template<class Mutex> explicit shared_lock(Mutex & m): pv((m.lock(), &m), mem_fn(&Mutex::unlock)) {}
};

shared_lock は次のようにして利用できる:

    shared_lock lock(m);

shared_lock はミューテックス型によってテンプレート展開されるクラスではないことに注意。 shared_ptr<void> の型情報を掩蔽する機能のお陰である。

shared_ptr を利用してメンバ関数の呼び出しをラップする

shared_ptr は Bjarne Stroustrup の著書 "Wrapping C++ Member Function Calls" ( オンラインで入手可能 http://www.research.att.com/~bs/wrapper.pdf) で説明されている Wrap/CallProxy 体系に要求される所有権のセマンティクスを実装している。 実装例は以下のように与えられる:

template<class T> class pointer
{
private:

    T * p_;

public:

    explicit pointer(T * p): p_(p)
    {
    }

    shared_ptr<T> operator->() const
    {
        p_->prefix();
        return shared_ptr<T>(p_, mem_fn(&T::suffix));
    }
};

class X
{
private:

    void prefix();
    void suffix();
    friend class pointer<X>;
    
public:

    void f();
    void g();
};

int main()
{
    X x;

    pointer<X> px(&x);

    px->f();
    px->g();
}

デアロケーションの遅延

パフォーマンスが非常に重要な分野において、 状況によっては、単一の px.reset()処理の重いデアロケーションを引き起こすことが問題になるかも知れない:

class X; // ~X の呼び出しコストが非常に高いとする

class Y
{
    shared_ptr<X> px;

public:

    void f()
    {
        px.reset();
    }
};

この解決法は、所有権を px から専用の解放リストへと移動させることにより、 デアロケーションの可能性を延期させるというものである。 解放リストは、パフォーマンスや応答時間が問題にならないタイミングで定期的に空にする:

vector< shared_ptr<void> > free_list;

class Y
{
    shared_ptr<X> px;

public:

    void f()
    {
        free_list.push_back(px);
        px.reset();
    }
};

// 都合の良いタイミングで定期的に free_list.clear() を実行する

もう一つのバリエーションとして、 shared_ptr の構築時点で、 廃棄リストへの移動のロジックを削除子(deleter)に組み込むという方法もある:

struct delayed_deleter
{
    template<class T> void operator()(T * p)
    {
        try
        {
            shared_ptr<void> pv(p);
            free_list.push_back(pv);
        }
        catch(...)
        {
        }
    }
};

shared_ptr に管理されていないオブジェクトを指す弱参照ポインタ

null_deleter を利用して、自身を指す shared_ptr オブジェクトを作成する:

class X
{
private:

    shared_ptr<X> this_;
    int i_;

public:

    explicit X(int i): this_(this, null_deleter()), i_(i)
    {
    }
    // 全てのコンストラクタ(コピーコンストラクタも含む!)で構築リストに
    // 同様な this_ の初期化宣言を記述する
	
    X(X const & rhs): this_(this, null_deleter()), i_(rhs.i_)
    {
    }
// コピー代入時に this_ を割り当てない事を忘れないようにせよ X & operator=(X const & rhs) { i_ = rhs.i_; } weak_ptr<X> get_weak_ptr() const { return this_; } };

このオブジェクトの生存期間が終了したときに X::this_ が破棄され、 全ての弱参照ポインタは自動的に無効になる


$Date: 2003/08/26 15:58:35 $

Copyright (c) 2003 Peter Dimov. Permission to copy, use, modify, sell and distribute this document is granted provided this copyright notice appears in all copies. This document is provided "as is" without express or implied warranty, and with no claim as to its suitability for any purpose.

Japanese Translation Copyright (C) 2003 Ryo Kobayashi.
オリジナルの、及びこの著作権表示が全ての複製の中に現れる限り、この文書の 複製、利用、変更、販売そして配布を認める。このドキュメントは「あるがまま」 に提供されており、いかなる明示的、暗黙的保証も行わない。また、 いかなる目的に対しても、その利用が適していることを関知しない。