2011年6月15日水曜日

続・スマートポインタの問題(?

前回の続き。

と、その前に前回少し触れた weak_ptr について軽く説明しておきます。
weak_ptr の元々の用途としては、shared_ptr が指し示すクラスが自身を差すポインタを持つ次のような場合、
class Hoge
{
public :
    shared_ptr<Hoge>  self_ptr;
};

void Foo()
{
    shared_ptr<Hoge> ptr( new Hoge ); // ①
    ptr->self_ptr = ptr;              // ②
}                                     // ③
関数 Foo() を抜けても、構築された Hoge は解放されません。①の時点でポインタの参照数は1です。②でコピーされ、参照数は2になります。そして、③の時点で ptr は解放され、参照数は1になります。つまり、ptr はスコープを抜けて破棄されますが、コピーされた self_ptr は破棄される機会がないのです。

これを解決するために、自身を差すようなポインタを持つ場合は weak_ptr というクラスを使用しましょうということになりました。weak_ptr は参照数を+1しないコピーであり、ここから新しいコピーを作成することもできます。
class Hoge
{
public :
    weak_ptr<Hoge>  self_ptr;
};

void Foo()
{
    shared_ptr<Hoge> ptr( new Hoge );                // ①
    ptr->self_ptr = ptr;                             // ②
    shared_ptr<Hoge> new_ptr = ptr->self_ptr.lock(); // ③
}                                                    // ④
①の時点で参照数は1です。②では、weak_ptr を作っているだけなので、参照数は変わらず1のままです。③では weak_ptr のメンバ関数 lock() を呼ぶことでこのスマートポインタのコピーを作成しているので参照数が2になります。そして、④では ptr 及び new_ptr がスコープから外れるので参照数は-2され0になります。このため、ここで Hogeのポインタは破棄されます。

ただし weak_ptr は参照数を+1しないため、参照数が0になった時点でポインタが破棄され、コピーが作成できなくなります。ですので、weak_ptr が外部にあるときには、正常に取得できたかどうか確認する必要があります。
void Foo()
{
    shared_ptr<Hoge> ptr( new Hoge );       // ①
    weak_ptr<Hoge> wptr( ptr );             // ②
    ptr.reset();                            // ③
    shared_ptr<Hoge> new_ptr = wptr.lock(); // ④
}
①②は前の例とおなじで、参照数はここまでで1です。そして、③の時点で ptr が参照数を-1するので参照数は0になり Hoge は破棄されます。次の④でコピーを作成しようとしますが、ポインタはすでに破棄されていますので、返されるポインタは null になります。ですので、このような使い方をする場合には、取得されたポインタが有効かどうかを確認してから使う必要があります。

さて、この weak_ptr を使えば前回言ったような「外部に渡すためのポインタ」が確保できるようになりますが、これは非常に面倒な作りです。上の例の Hoge は「必ず new で確保しなければならない」「shared_ptr で管理しなければならない」「構築した後、self_ptr を作成しなければならない」という制限がついて回ることになります。これらをまとめて行う関数を作ることはできますが、もし Hoge の派生クラスを作ると、その派生クラス用の構築関数も作る必要が出てきます。また、このクラスの配列を構築したい場合、スタックに構築したい場合にも対応できません。

これらのことから、weak_ptr は良い解決方法とは言えない、と思われるわけです。

…何か長くなってきたので続く

0 件のコメント:

コメントを投稿