最終更新日時:
が更新

履歴 編集

shared_ptr class template

Introduction

shared_ptrクラステンプレートは、C++のnewなどによって動的に割り当てられたオブジェクトへのポインタを保持する。 shared_ptrに指されたオブジェクトは、そのオブジェクトを指す最後のshared_ptrが破棄もしくはリセットされるときに削除されることが保証されている。 exampleを参照のこと。

shared_ptrはC++標準ライブラリのCopyConstructible(コピーコンストラクト可能)とAssignable(代入可能)の条件を満たすので、標準ライブラリのコンテナで使うことができる。 また、標準ライブラリの連想コンテナで使うことができるように、比較演算子が提供されている。

通常、shared_ptrは動的に割り当てられた配列を正しく扱うことはできない。 動的に割り当てられた配列の扱い方については、shared_arrayを参照のこと。

shared_ptrの実装には参照カウントが用いられているため、循環参照されたshared_ptrのインスタンスは正常に解放されない。 例えば、main()Aを指すshared_ptrを保持しているときに、そのAが直接的または間接的にA自身を指すshared_ptrを持っていると、Aに対する参照カウントは2となる。 最初のshared_ptrが破棄される際に、A参照カウントは 1 となり、そのインスタンスは破棄されずに残ってしまう。 循環参照を回避するには、weak_ptrを使う。

このクラステンプレートには、指し示すオブジェクトの型を表すパラメータTを与える。 shared_ptrとそのメンバ関数の多くは、Tに特別な条件を必要としない。 不完全型やvoidも許されている。 Tに特別な条件を必要とするメンバ関数(constructors, reset)についてはこのドキュメント中で明示されている。

T *が暗黙の型変換によりU *に変換可能であれば、shared_ptr<T>は暗黙にshared_ptr<>に変換できる。 特に、shared_ptr<T>は暗黙の型変換により、shared_ptr<T const>shared_ptr<U>shared_ptr<void>に変換できる。 (Uはアクセス可能なTの基底型)

Best Practices

メモリリークの可能性をほとんど排除する為のシンプルな指針 : newの結果を常に名前のあるスマートポインタに格納すること。 コードに含まれる全てのnewキーワードは、次の形にされるべきである :

shared_ptr<T> p(new Y);

もちろん、上でのshared_ptrの代わりに他のスマートポインタを利用しても良い。 また、TYが同じ型であったり、Yのコンストラクタに引数が与えられても良い。

この指針に従えば、自然と明示的なdeleteが無くなり、try/catch構文も極めて少なくなるだろう。

タイプ数(コード量)を減らすために、名前のない一時的なshared_ptrを使ってはならない。 このことがなぜ危険かを理解するには、以下の例を考えると良い :

void f(shared_ptr<int>, int);
int g();

void ok()
{
    shared_ptr<int> p(new int(2));
    f(p, g());
}

void bad()
{
    f(shared_ptr<int>(new int(2)), g());
}

ok関数はこの指針に的確に従っているのに対し、bad関数は一時的なshared_ptrを使用しており、メモリリークが起きる可能性がある。 関数の引数が評価される順序が不定であるため、new int(2)が最初に評価され、次にg()が評価されるかもしれない。 その結果、もしgが例外を送出すると、shared_ptrのコンストラクタは呼び出されない。 この問題についてのより詳しい情報はHerb Sutter's treatment (英文)を参照のこと。

Synopsis

namespace boost {

class use_count_is_zero: public std::exception;

template<typename T> class weak_ptr;

template<typename T> class shared_ptr {

public:

    typedef T element_type;

    shared_ptr();
    template<typename Y> explicit shared_ptr(Y * p);
    template<typename Y, typename D> shared_ptr(Y * p, D d);
    ~shared_ptr(); // never throws

    shared_ptr(shared_ptr const & r); // never throws
    template<typename Y> shared_ptr(shared_ptr<Y> const & r); // never throws
    template<typename Y> explicit shared_ptr(weak_ptr<Y> const & r);
    template<typename Y> explicit shared_ptr(std::auto_ptr<Y> & r);

    shared_ptr & operator=(shared_ptr const & r); // never throws  
    template<typename Y> shared_ptr & operator=(shared_ptr<Y> const & r); // never throws
    template<typename Y> shared_ptr & operator=(std::auto_ptr<Y> & r);

    void reset();
    template<typename Y> void reset(Y * p);
    template<typename Y, typename D> void reset(Y * p, D d);

    T & operator*() const; // never throws
    T * operator->() const; // never throws
    T * get() const; // never throws

    bool unique() const; // never throws
    long use_count() const; // never throws

    operator unspecified-bool-type() const; // never throws

    void swap(shared_ptr & b); // never throws
};

template<typename T, typename U>
bool operator==(shared_ptr<T> const & a, shared_ptr<U> const & b); // never throws
template<typename T, typename U>
bool operator!=(shared_ptr<T> const & a, shared_ptr<U> const & b); // never throws
template<typename T>
bool operator<(shared_ptr<T> const & a, shared_ptr<T> const & b); // never throws

template<typename T>
void swap(shared_ptr<T> & a, shared_ptr<T> & b); // never throws

template<typename T>
T * get_pointer(shared_ptr<T> const & p); // never throws

template<typename T, typename U>
shared_ptr<T> shared_static_cast(shared_ptr<U> const & r); // never throws
template<typename T, typename U>
shared_ptr<T> shared_dynamic_cast(shared_ptr<U> const & r);
template<typename T, typename U>
shared_ptr<T> shared_polymorphic_cast(shared_ptr<U> const & r);
template<typename T, typename U>
shared_ptr<T> shared_polymorphic_downcast(shared_ptr<U> const & r); // never throws

}

[shared_ptrのシグネチャに必要な条件を緩和し、補足的なデフォルトのテンプレートパラメータ(例えば、スレッドモデルを変換可能なパラメータなど)を使えるようにすることは、利便性の向上に繋がるかも知れない。 これは、ODR違反の可能性を発見する一助になるだろう。 (訳注:ODR(One-Definition Rule) C++ のプログラム中のあらゆる要素の本体は、その要素が使われる全ての翻訳単位で同じ内容で定義されなくてはならないという規則[参考(boost::pythonのドキュメント)])

一方、shared_ptrをtemplateテンプレートパラメータとして使うには、シグネチャの正確な合致が必要となる。 メタプログラミングに精通している人は、template テンプレートパラメータを重要視しない。 柔軟性が低すぎるからである。 その代わり典型的に、std::allocator::rebind-typeを"書き換える"。]

Members

element_type

typedef T element_type;

テンプレートパラメータ T の型を規定する

コンストラクタ ( constructors )

shared_ptr();

  • Effects:
    • shared_ptrを構築する。
  • Postconditions:
    • use countは 1 ; 保持されるポインタは 0 。
  • Throws:
    • std::bad_alloc.
  • Exception safety:
    • 例外が発生すると、コンストラクタは何もしない。

[use_count() == 1という事後条件は強すぎる。 reset()の中でデフォルトコンストラクタが使われるため、例外を送出しない保証が重要である。 しかし、現在の仕様では参照カウンタの割り当てが必要となっているため、例外を送出しないことが保証されなくなっている。 そのため、この事後条件は将来のリリースで撤廃されるだろう。 デフォルトコンストラクタにより構築されたshared_ptr(とそこから作られた全てのコピー)の参照カウンタは、おそらく未定義になるだろう。

例外を送出しないことを保証するには、二つの実装が考えられる。 一つは、参照カウンタへのポインタとして0を保持する方法、もう一つは、デフォルトコンストラクタによって構築される全てのshared_ptrに対して、静的に割り当てられた唯一の参照カウンタを利用する方法である。 後者の方法は、スレッドセーフの問題と初期化の順序の問題のために、現在のヘッダのみの参照実装では実現が困難であるが仕様の為に実装方法が制限されるべきではない。

将来のリリースでは、組み込みポインタとの一貫性を高めるため、shared_ptrを数字の0から構築できるようになるかもしれない。 今後、0shared_ptr<T>()の略記として使うことを可能にする、このコンストラクタが、潜在化されたままにされるかどうかは明かではない。]

template<typename Y>
explicit shared_ptr(Y * p);

  • Requirements:
    • pT *に変換可能でなくてはならない。
    • Yは完全な型でなくてはならない。
    • delete pの式が文法的に正しくなければならない; 未定義の振る舞いをしてはならない; 例外を送出してはならない。
  • Effects:
    • shared_ptrを構築し、pのコピーを保持する。
  • Postconditions:
  • Throws:
    • std::bad_alloc.
  • Exception safety:
    • 例外が発生すると、delete pを呼び出す。
  • Notes:
    • pはC++のnewによって割り当てられたオブジェクトへのポインタか、0でなくてはならない。 事後条件のuse countが1というのは、pが0の時でも同様である(値が0のポインタに対するdelete呼び出しが安全であるため )。

[このコンストラクタは、実際に渡されたポインタの型を記憶するためにテンプレートに変更された。 デストラクタは同じポインタについて、本来の型でdeleteを呼び出す。 よって、Tが仮想デストラクタを持っていなくても、あるいはvoidであっても、本来の型でdeleteされる。

現在の実装では、pcounted_base *に変換可能なとき、shared_ptrcounted_baseに埋め込まれた参照カウントを使う。 これは、shared_ptrthisのような生のポインタから構築する方法を提供する(実験的な)試みである。 非メンバ関数shared_from_this(q)は、qcounted_base const *へ変換可能なとき、その変換を行う。

現在の実装で用意されている随意選択可能な割り込みカウントは、shared_ptrintrusive_ptr(割り込みカウント方式の実験的な汎用スマートポインタ)と一緒に利用できるようにしている。

別の実装の可能性としては、割り込みカウントではなくグローバルのポインタカウントマップを使う方法が考えられる。 その場合、shared_from_thisの処理時間はO(1)ではなくなる。 これは一部のユーザに影響を与えるが、この処理が行われることは希なため、パフォーマンスの問題は予想していない。 グローバルのポインタカウントマップを管理するのは困難である; ポインタカウントマップはshared_ptrのインスタンスが構築される前に初期化されている必要があり、初期化はスレッドセーフに行われなければならない。 Windowsの動的ライブラリの形態に従えば、幾つかのカウントマップを存在させることができる。

どの実装が使われるべきか、または仕様でその両方を許容するかどうかは、まだ明かではない。 とは言え、スマートポインタを幅広く利用するプログラマにとって、shared_ptrthisから構築できることは必要不可欠である。]

template<typename Y, typename D>
shared_ptr(Y * p, D d);

  • Requirements:
    • pT *に変換可能でなくてはならない。 DCopyConstructible(コピーコンストラクト可能)でなくてはならない。 Dのコピーコンストラクタとデストラクタは例外を送出してはならない。 d(p)の式が文法的に正しくなければならない; 未定義の振る舞いをしてはならない; 例外を送出してはならない。
  • Effects:
    • shared_ptrを構築し、pdのコピーを保持する。 (訳注: dpdeallocator(削除子)になる)
  • Postconditions:
  • Throws:
    • std::bad_alloc
  • Exception safety:
    • 例外が発生すると、d(p)を呼び出す。
  • Notes:
    • pに指されているオブジェクトを削除する時になると、保持されているpのコピーを1引数として、保持されているd(のコピー)が実行される。

[カスタム削除子は、shared_ptrを返すファクトリ関数を利用可能にし、メモリ割り当ての方策をユーザから切り離す。 削除子は型の属性ではないので、バイナリの互換性やソースを破壊せずに変更することができ、使用する側の再コンパイルを必要としない。 例えば、静的に割り当てられたオブジェクトを指すshared_ptrを返すには、"何もしない(no-op)" 削除子が有効である。

カスタム削除子のサポートは大きなオーバーヘッドを生じない。 shared_ptrの他の特徴も削除子が保持されることを必要としている。

Dのコピーコンストラクタが例外を送出しないと言う条件は、値渡しのために設定されている。 もし、このコピーコンストラクタが例外を送出すると、ポインタpが指すメモリがリークする。 この条件を取り除くためには、dを(コンストの)参照渡しにする必要がある。 参照渡しには幾つかの短所がある; (1) 値渡しならば、関数(関数への参照)を関数ポインタ(幾つかのコンパイラではできないかもしれないが、手動で実行できる必要がある)に変更するのが容易である。 (2) 現在のところ、(標準に従えば)コンスト参照を関数に結びつけることはできない。 オーバーロード関数群を備えることでこれらの制限を克服できるのだが、幾つかのコンパイラに存在する14.5.5.2 問題のために実現できない。 14.5.5.2 問題とは、部分整列をサポートしていないコンパイラで、特殊化されたテンプレート関数がコンパイルできないというものである。 (訳注: "部分整列" : テンプレート関数の特殊化の度合いによる利用優先順位付け)

*前述された問題が解決されれば、これらの条件も取り除かれるだろう。] *

shared_ptr(shared_ptr const & r); // never throws
template<typename Y>
shared_ptr(shared_ptr<Y> const & r); // never throws

  • Effects:
    • shared_ptrを構築し、rが保持するポインタのコピーを保持したかのように作用する。
  • Postconditions:
    • 全てのコピーのuse countは 1 増加する。
  • Throws:
    • 無し。

[デフォルトコンストラクタにより構築されたshared_ptrは、コピーされると事後条件が緩和される。]

template<typename Y>
explicit shared_ptr(weak_ptr<Y> const & r);

  • Effects:
    • shared_ptrを構築し、r`が管理するポインタのコピーを保持したかのように作用する。
  • Postconditions:
    • 全てのコピーのuse countは 1 増加する。
  • Throws:
    • r.use_count() == 0の時、use_count_is_zeroを送出する。
  • Exception safety:
    • 例外が発生すると、コンストラクタは何もしない。

[このコンストラクタは仕様の選択的な部分に位置する; weak_ptrの存在に依存する。 weak_ptrが使用されているかどうかに無頓着なユーザにとって、weak_ptrのサポートがshared_ptrにオーバーヘッドを生じさせているのは事実である。

一方、全ての参照カウントにとって、循環参照は深刻な問題である。 ライブラリ内で解決方法が提供されないのは許容できない; もしユーザがウィークポインタ機構の再開発をせざるを得なくなった場合、安全なweak_ptrの設計は簡単なことではなく、悪い結果をもたらす確率は相当大きい。

機能の追加には努力を払う価値があるというのが私の意見である。 その証拠として、この参照の実装にてweak_ptrが提供されている。]

template<typename Y>
shared_ptr(std::auto_ptr<Y> & r);

  • Effects:
    • shared_ptrを構築し、r.release()のコピーを保持したかのように作用する。
  • Postconditions:
  • Throws:
    • std::bad_alloc
  • Exception safety:
    • 例外が発生すると、コンストラクタは何もしない。

[このコンストラクタはauto_ptrを値渡しでなく参照で受け取り、一時的なauto_ptrを受け取らない。 これは、このコンストラクタが強力な保証を提供する設計にするためである。]

デストラクタ ( destructor )

~shared_ptr(); // never throws

  • Effects:
    • もし *this が唯一の所有者であるとき(use_count() == 1)、保持しているポインタが指すオブジェクトを破棄する。
  • Postconditions:
    • 残存する全てのコピーのuse countが 1 減少する。
  • Throws:
    • なし。

代入 ( assignment )

shared_ptr & operator=(shared_ptr const & r); // never throws
template<typename Y>
shared_ptr & operator=(shared_ptr<Y> const & r); // never throws
template<typename Y>
shared_ptr & operator=(std::auto_ptr<Y> & r);

  • Effects:
    • shared_ptr(r).swap(*this)と等価。
  • Notes:
    • 一時的なスマートポインタの構築と破棄による参照カウントの更新は未知の副作用を生じる可能性がある。 この実装は、一時的なオブジェクトを構築しない方法を採ることによって、 保証された作用を得られる。 特に、この様な例では:

shared_ptr<int> p(new int);
shared_ptr<void> q(p);
p = p;
q = p;

  いずれの代入文も、何も作用しない(no-op)だろう。

[一部の上級者は、この"as if"規則(訳注: 演算子の再配置規則)をそのまま表現したような注意書きをくどいと感じるだろう。 しかし、作用の説明に C++ のコードを用いられるとき、しばしばそれが必要な実装であるかのように誤って解釈されてしまうことがあると、経験的に示唆されている。 さらに付け加えると、この部分で"as if"規則が適用されるかどうかは全くわからないが、可能な最適化について明示しておくことは好ましいと思われる。]

リセット ( reset )

void reset();

  • Effects:
    • shared_ptr().swap(*this)と等価。
    • [reset()は将来の実装で、例外を送出しない(nothrow)保証を提供するだろう。]

template<typename Y>
void reset(Y * p);

  • Effects:
    • shared_ptr(p).swap(*this)と等価。

template<typename Y, typename D>
void reset(Y * p, D d);

  • Effects:
    • shared_ptr(p, d).swap(*this)と等価。

ポインタ偽装 ( indirection )

T & operator*() const; // never throws

  • Requirements:
    • 保持されているポインタが 0 でないこと。
  • Returns:
    • 保持されているポインタが指すオブジェクトの参照。
  • Throws:
    • 無し。

T * operator->() const; // never throws

  • Requirements:
    • 保持されているポインタが 0 でないこと。
  • Returns:
    • 保持されているポインタ。
  • Throws:
    • 無し。

ポインタの取得 ( get )

T * get() const; // never throws

  • Returns:
    • 保持されているポインタ。
  • Throws:
    • 無し。

一意性 ( unique )

bool unique() const; // never throws

  • Returns:
    • use_count() == 1.
  • Throws:
    • 無し。
  • Notes:
    • unique()は恐らくuse_count()よりも速い。 だが、もしunique()を使って書き込み時コピー(copy on write)を実装しようとしているなら、保持されているポインタが0の時はunique()の値を当てにしてはならない。

[将来のリリースでは、デフォルトコンストラクタで構築されたshared_ptrに対し、unique()は不定の値を返すようになるだろう。]

参照カウント ( use_count )

long use_count() const; // never throws

  • Returns:
    • 保持しているポインタを共有しているshared_ptrオブジェクトの数。
  • Throws:
    • 無し。
  • Notes:
    • use_count()は必ずしも必要なものではない。 デバッグや試験の為にだけ使用するべきで、製品のコードに使用するべきでない。

変換 ( conversions )

operator unspecified-bool-type () const; // never throws

  • Returns:
    • shared_ptrがブール式として使用されたときに、get() != 0と等価な明示的ではない値を返す。
  • Throws:
    • 無し。
  • Notes:
    • この変換演算子はshared_ptrオブジェクトを、if (p && p->valid()) {}のようなブール式の中で使えるようにするためのものである。
    • 実際に対象となる型はメンバ関数へのポインタなどであり、暗黙の型変換の落とし穴を回避するために用いる。

[このブールへの変換は単にコードをスマートにする物(syntactic sugar : 構文糖)というわけではない。 この変換によりshared_dynamic_castmake_sharedを使用するときに、shared_ptrを条件式として利用することができる。]

交換 ( swap )

void swap(shared_ptr & b); // never throws

  • Effects:
    • 二つのスマートポインタの中身を交換する。
  • Throws:
    • 無し。

Free Functions

比較 ( comparison )

template<typename T, typename U>
bool operator==(shared_ptr<T> const & a, shared_ptr<U> const & b); // never throws

  • Returns:
    • a.get() == b.get()
  • Throws:
    • 無し。

template<typename T, typename U>
bool operator!=(shared_ptr<T> const & a, shared_ptr<U> const & b); // never throws

  • Returns:
    • a.get() != b.get()
  • Throws:
    • 無し。

template<typename T>
bool operator<(shared_ptr<T> const & a, shared_ptr<T> const & b); // never throws

  • Returns:
    • operator<は、C++ 標準の[lib.alg.sorting]の25.3章で説明されている、完全な弱い順序づけのための明示的ではない値を返す。
  • Throws:
    • 無し。
  • Notes:
    • shared_ptrオブジェクトを連想コンテナのキーとして使えるようにするための演算子。

[一貫性と適合性の理由から、std::lessの特殊化版よりも、operator<の方が好まれて使われている。 std::lessoperator<の結果を返すことを必要とされ、他の幾つかの標準アルゴリズムも、属性が提供されないとき、比較のためにstd::lessではなくoperator<を使う。 std::pairのような複合オブジェクトのoperator<もまた、収容している子オブジェクトのoperator<に基づいて実装されている。

比較演算子の安全の確保は、設計によって省略された。]

交換 ( swap )

template<typename T>
void swap(shared_ptr<T> & a, shared_ptr<T> & b); // never throws

  • Effects:
    • a.swap(b)と等価。
  • Throws:
    • 無し。
  • Notes:
    • std::swapのインターフェースとの一貫性を図り、ジェネリックプログラミングを支援する。

[swapshared_ptrと同じ名前空間で定義される。 これは現在のところ、標準ライブラリから使用可能なswap関数を提供するための唯一の正当な方法である。]

ポインタを取得 ( get_pointer )

template<typename T>
T * get_pointer(shared_ptr<T> const & p); // never throws

  • Returns:
    • p.get()
  • Throws:
    • 無し。
  • Notes:
    • 汎用プログラミングを補助する機能を提供する。 mem_fnで使用する。

静的キャスト ( shared_static_cast )

template<typename T, typename U>
shared_ptr<T> shared_static_cast(shared_ptr<U> const & r); // never throws

  • Requires:
    • static_cast<T*>(r.get())は正しい形でなくてはならない。
  • Returns:
    • static_cast<T*>(r.get())のコピーを保持し、rと所有権を共有するshared_ptr<T>オブジェクト。
  • Throws:
    • 無し。
  • Notes:
    • 表面的には次の式と等価。 shared_ptr<T>(static_cast<T*>(r.get())) これは、同じオブジェクトを2度削除しようとする事になるため、結局は未定義のふるまいとなる。

動的キャスト ( shared_dynamic_cast )

template<typename T, typename U>
shared_ptr<T> shared_dynamic_cast(shared_ptr<U> const & r);</pre>

  • Requires:
    • dynamic_cast<T*>(r.get())の式が正しい形であり、そのふるまいが定義されていなくてはならない。
  • Returns:
    • dynamic_cast<T*>(r.get())が非ゼロの値を返すとき、rのコピーを保持し、その所有権を共有するshared_ptr<T>オブジェクトを返す。
    • それ以外の時は、デフォルトコンストラクタにより構築されたshared_ptr<T>オブジェクトを返す。
  • Throws:
    • std::bad_alloc
  • Exception safety:
    • 例外が発生すると、この関数は何もしない。
  • Notes:
    • 表面的には次の式と等価。 shared_ptr<T>(dynamic_cast<T*>(r.get())) これは、同じオブジェクトを2度削除しようとする事になるため、結局は未定義のふるまいとなる。

ポリモーフィックキャスト ( shared_polymorphic_cast )

template<typename T, typename U>
shared_ptr<T> shared_polymorphic_cast(shared_ptr<U> const & r);

  • Requires:
    • polymorphic_cast<T*>(r.get())の式が正しい形であり、そのふるまいが定義されていなくてはならない。
  • Returns:
    • polymorphic_cast<T*>(r.get())のコピーを保持し、rと所有権を共有するshared_ptr<T>オブジェクト。
  • Throws:
    • 保持しているポインタが変換できないとき、std::bad_castを送出する。
  • Exception safety:
    • 例外が発生すると、この関数は何もしない。

ポリモーフィックダウンキャスト ( shared_polymorphic_downcast )

template<typename T, typename U>
shared_ptr<T> shared_polymorphic_downcast(shared_ptr<U> const & r); // never throws

  • Requires:
    • polymorphic_downcast<T*>(r.get())の式が正しい形であり、そのふるまいが定義されていなければならない。
  • Returns:
    • polymorphic_downcast<T*>(r.get())のコピーを保持し、rと所有権を共有するshared_ptr<T>オブジェクト。
  • Throws:
    • 無し。

Example

サンプルプログラムの本体はshared_ptr_example.cppを参照のこと。 このプログラムは、shared_ptrオブジェクトからなるstd::setstd::vectorを作成する。

これらのコンテナにshared_ptrオブジェクトを格納した後、幾つかのshared_ptrオブジェクトの参照カウントが2ではなく1になることに注意せよ。 これは、コンテナとしてstd::multisetではなくstd::setが使われているためである(std::setは重複するキーを持つ要素を受け入れない)。 更に言うと、これらのオブジェクトの参照カウントはpush_back及びinsertのコンテナ操作をしている間は同じ数のままであるだろう。 更に複雑になると、コンテナ操作の際に様々な要因によって例外が発生する可能性もある。 スマートポインタを利用せずにこの様なメモリ管理や例外管理を行うことは、正に悪夢である。

Handle/Body Idiom

shared_ptrの一般的な用法の一つに、handle/body表現(pimplとも呼ばれる)の実装がある。 handle/body表現とは、オブジェクト本体の実装を隠蔽する(ヘッダファイル中にさらけ出すことを回避する)ためのものである。

サンプルプログラムshared_ptr_example2_test.cppは、ヘッダファイルshared_ptr_example2.hppをインクルードしている。 このヘッダファイルでは、不完全型のポインタを取るshared_ptr<>を利用して実装を隠蔽している。 完全型が必要となるメンバ関数のインスタンス化は、実装ファイルshared_ptr_example2.cpp内に記述されている。 ここでは明示的なデストラクタが必要とされていないことに注意せよ。 ~scoped_ptrと違い、~shared_ptrTは完全型である必要はない。

Thread Safety

shared_ptrオブジェクトはプリミティブ型と同等のスレッドセーフティを提供する。 shared_ptrのインスタンスは、複数のスレッドから(const 処理のためのアクセスに限り)同時に"読む"事ができる。 また、異なるshared_ptrを、複数のスレッドから(operator=resetのようなスレッド動作を想定した操作のためのアクセスに限り)同時に"変更する"こともできる (それらのshared_ptrインスタンスが、コピーされた(同じ参照カウントを共有する)ものでも問題ない )。

上記以外の同時アクセスは未定義のふるまいを引き起こす。

例 Examples:

shared_ptr<int> p(new int(42));

//--- Example 1 ---

// thread A
shared_ptr<int> p2(p); // reads p

// thread B
shared_ptr<int> p3(p); // OK, multiple reads are safe

//--- Example 2 ---

// thread A

p.reset(new int(1912)); // writes p

// thread B
p2.reset(); // OK, writes p2

//--- Example 3 ---

// thread A
p = p3; // reads p3, writes p

// thread B
p3.reset(); // writes p3; undefined, simultaneous read/write

//--- Example 4 ---

// thread A
p3 = p2; // reads p2, writes p3

// thread B
// p2 goes out of scope: undefined, the destructor is considered a "write access"

//--- Example 5 ---

// thread A
p3.reset(new int(1));

// thread B
p3.reset(new int(2)); // undefined, multiple writes

shared_ptrは、実装がスレッドをサポートしているかどうかを検出するためにBoost.Configを使用している。 もしあなたのプログラムがシングルスレッドだとしても、マルチスレッドをサポートしているかどうかはBoost.Configが自動的に検出する。 シングルスレッドのプロジェクトにおいて、スレッドセーフティの為のオーバーヘッドを取り除くためには、#define BOOST_DISABLE_THREADSを定義する。

FAQ ( Frequently Asked Questions )

Q. 共有ポインタにはそれぞれ異なる特長を持った幾つかの実装のバリエーションがあるが、なぜこのスマートポインタライブラリは単一の実装しか提供しないのか? 手元の仕事に最も適した実装を見つけるために、それぞれの型を試してみられることは有益なのではないだろうか?

A. 標準的な所有権共有ポインタを提供することが、shared_ptrの重要な目標の一つである。 通常、異なる共有ポインタは併用できないので、安定したライブラリインターフェースを提供するためには共有ポインタ型を一つにすることが大切である。 例えば、(ライブラリAで使われている)参照カウントポインタは、(ライブラリBで使われている)連結ポインタと所有権を共有できない。

Q. なぜshared_ptrは、拡張のためのポリシーや特性を与えるためのテンプレートパラメータを持たないのか。

A. パラメータ化することは、ユーザにとって使いにくくなることに繋がる。 このshared_ptrテンプレートは、拡張可能なパラメータを必要とせずに一般的なニーズを満たすように注意深く設計されている。 いつかは、高い拡張性を持ち、非常に使い易く、且つ誤用されにくいスマートポインタが開発されるかも知れない。 しかしそれまでは、shared_ptrが幅広い用途に使用されるだろう。 (そのような興味深いポリシー思考のスマートポインタについて知りたければ、Andrei AlexandrescuのModern C++ Designを読むべきである。)

Q. 私は納得できない。 複雑性を隠すためにデフォルトのパラメータを使うことができるはずだ。 もう一度尋ねるが、なぜポリシーを導入しないのか?

A. テンプレートパラメータは型に影響を及ぼす。 この FAQ の最初の解答を参照せよ。

Q. なぜshared_ptrの実装は連結リスト方式を使っていないのか?

A. 連結リスト方式の実装は、余分なポインタのためのコストに見合うだけの利点が無いからである。 timingsのページを参照せよ。 補足すると、連結リスト方式の実装でスレッドセーフティを実現するには、大きな犠牲を伴う。

Q. なぜshared_ptrやその他のBoostスマートポインタは、T *への自動的な型変換を提供しないのか?

A. 自動的な型変換は、エラーに繋がる傾向が非常に高いと信じられている。

Q. なぜshared_ptruse_count()を提供しているのか?

A. テストケースを書くための支援や、デバッグ出力の支援をするためである。 循環依存することが分かっているような複雑なプログラムにおいて、原本となるshared_ptruse_count()が、バグを追跡するために有効である。

Q. なぜshared_ptrは計算量の指定を明示しないのか?

A. なぜなら、計算量の指定は、実装者に制限を付与し、shared_ptrの利用者に対する見かけ上の利益もなしに仕様を複雑化する。 例えば、もしエラー検証機構の実装に厳密な計算量の指定が必要とされた場合、その実装には整合性が無くなってしまうだろう。

Q. なぜshared_ptrrelease()関数を提供しないのか?

A. shared_ptrunique()な時をのぞいて、所有権を譲渡できない。 なぜなら、いずれは所有権を共有している他のshared_ptrが、そのオブジェクトを削除するはずだからである。

考えてみよ:

shared_ptr<int> a(new int);
shared_ptr<int> b(a); // a.use_count() == b.use_count() == 2

int * p = a.release();
// このとき、pの所有権はどこにあるのだろう?aがrelease()してもなお、bはデストラクタの中でdeleteを呼ぶだろう。

Q. なぜshared_ptrは(あなたが大好きな機能をここに当てはめよ)を提供しないのか?

A. なぜなら、(あなたが愛する機能)は、参照カウント方式の実装でも、連結リスト方式の実装でも、あるいは他の特定の実装でも構わないという話だったからである。 故意に提供していないわけではない。


Revised $Date: 2003/03/15 06:38:54 $

Copyright 1999 Greg Colvin and Beman Dawes. Copyright 2002 Darin Adler. Copyright 2002 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, Kohske Takahashi.

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