c++boost.gif (8819 bytes)shared_ptr class template

Introduction
Best Practices
Synopsis
Members
Free Functions
Example
Handle/Body Idiom
Thread Safety
Frequently Asked Questions
Smart Pointer Timings
Programming Techniques

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<U> に変換できる。 特に、shared_ptr<T> は暗黙の型変換により、 shared_ptr<T const>shared_ptr<U>shared_ptr<void> に変換できる。 (Uはアクセス可能なTの基底型)

Best Practices

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

もちろん、上での 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 bad_weak_ptr: public std::exception;

  template<class T> class weak_ptr;

  template<class T> class shared_ptr {

    public:

      typedef T element_type;

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

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

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

      void reset(); // never throws
      template<class Y> void reset(Y * p);
      template<class Y, class 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<class T, class U>
    bool operator==(shared_ptr<T> const & a, shared_ptr<U> const & b); // never throws

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

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

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

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

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

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

  template<class E, class T, class Y>
    std::basic_ostream<E, T> & operator<< (std::basic_ostream<E, T> & os, shared_ptr<Y> const & p);

  template<class D, class T>
    D * get_deleter(shared_ptr<T> const & p);
}

[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(); // never throws

Effects: 空の shared_ptr を構築する。 空の shared_ptruse_count は不定である。

Postconditions: get() == 0.

Throws: 無し。

[reset() の中でデフォルトコンストラクタが使われるため、例外を送出しない保証が重要である; 例外を送出しないために、このコンストラクタはメモリ確保を行ってはならないことになる。

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

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

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

Requirements: pT * に変換可能でなくてはならない。 Y は完全な型でなくてはならない。 delete p の式が文法的に正しくなければならない; 未定義の振る舞いをしてはならない; 例外を送出してはならない。

Effects: ポインタ p所有 する shared_ptr を構築する。

Postconditions: use_count() == 1 && get() == p.

Throws: 参照カウントのメモリ確保に失敗した場合は std::bad_alloc を送出する。 メモリ以外のリソースの確保に失敗した場合は実装定義の例外を送出する。

Exception safety: 例外が送出されると、delete p を呼び出す。

Notes: p はC++の new によって確保されたオブジェクトへのポインタか、 0でなくてはならない。 事後条件の use count が 1 というのは、 p が0の時でも同様である( 値が0のポインタに対する delete 呼び出しが安全であるため )。

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

補足的に導入された埋め込みカウント方式のサポートは、 実装の詳細を暴露しすぎてしまうのに加え、 weak_ptr との相互利用も宜しくない。 現在の実装は、異なるメカニズム enable_shared_from_this を用いて、this から shared_ptr を構築する際に生じる問題を解決している。 ]

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

Requirements: pT * に変換可能でなくてはならない。 DCopyConstructible (コピーコンストラクト可能)でなくてはならない。 D のコピーコンストラクタとデストラクタは例外を送出してはならない。 d(p) の式が文法的に正しくなければならない; 未定義の振る舞いをしてはならない; 例外を送出してはならない。

Effects: ポインタ p と削除子(deleter) d所有する shared_ptr を構築する。

Postconditions: use_count() == 1 && get() == p.

Throws: メモリ確保に失敗した場合は std::bad_alloc を送出する。 メモリ以外のリソースの確保に失敗した場合は実装定義の例外を送出する。

Exception safety: 例外が送出されると、d(p) が呼ばれる。

Notes: p に指されているオブジェクトを削除する時になると、 保持されている p のコピーを1引数として、保持されている d (のコピー)が実行される。

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

カスタム削除子(deleter)のサポートによる深刻なオーバーヘッドは生じない。 shared_ptr の他の機能も削除子(deleter)が保持されていることを必要としている。

D のコピーコンストラクタが例外を送出しないと言う条件は、 D のインスタンスを値渡しするためである。 もし、このコピーコンストラクタが例外を送出すると、ポインタ p が指すメモリがリークする。 この条件を取り除くためには、d を(コンスト)参照で渡す必要がある。

参照渡しには幾つかの問題が伴う; (1) 値渡しは関数(または関数への参照)を関数ポインタに変換しやすく (値渡し以外では、この変換を手動で行う必要があり、またコンパイラによってはその変換が許されないものもある) 、 (2) 現在のところ(標準に従うと)、コンスト参照を関数に結びつけることはできない。 これは解決できる(と思われる)が、14.5.5.2 の問題の為に多くのコンパイラでコンパイルできないオーバーロード関数群が必要となる。 (そしてもちろん、部分整列を全く受け付けないコンパイラではコンパイルできない) (訳注1: 14.5.5.2 問題とは、部分整列をサポートしていないコンパイラで、特殊化されたテンプレート関数がコンパイルできないというもの) (訳注2: "部分整列" : テンプレート関数の特殊化の度合いによる解決優先順位付け)

参照渡しの主要な問題は、右辺値との相互作用にある。 コンスト参照で受け取った関数オブジェクトには const 属性の operator() が必要となるが、もしこの条件を満たさない場合は、結果的にコピーを生じることになる。 また、非コンスト参照は右辺値と結合できない事があるだろう。 この問題に対する好ましい解法は、 N1377/N1385 で提案されている右辺値参照テクニックを使うことである。 ]

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

Effects: もしr空(empty) の場合は、空の shared_ptr を構築する。 そうでなければ、r所有権を共有する shared_ptr を構築する。

Postconditions: get() == r.get() && use_count() == r.use_count().

Throws: 無し。

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

Effects: もしr空(empty) の場合は、空の shared_ptr を構築する。 そうでなければ、r所有権を共有する shared_ptr を構築し、 r が保持しているポインタのコピーを格納する。

Postconditions: use_count() == r.use_count().

Throws: r.use_count() == 0 の時 bad_weak_ptr 例外を送出する。

Exception safety: 例外が送出されると、このコンストラクタは何も作用しない。

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

Effects: shared_ptr を構築し、r.release() のコピーを格納する。

Postconditions: use_count() == 1.

Throws: メモリ確保に失敗した場合は std::bad_alloc を送出する。 メモリ以外のリソースの確保に失敗した場合は実装定義の例外を送出する。

Exception safety: 例外が送出されると、このコンストラクタは何も作用しない。

[ このコンストラクタは、元になる auto_ptr を値渡しではなく参照渡しで受け取る。 ただし、一時的(テンポラリ)な auto_ptr は受け付けない。 これは設計によるもので、このコンストラクタに強い保証を持たせる為の仕様である; この問題も前述の前述の右辺値参照テクニックにより解決されるだろう。 ]

destructor

~shared_ptr(); // never throws

Effects:

Throws: 無し。

assignment

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

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

Returns: *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(); // never throws

Effects: Equivalent to shared_ptr().swap(*this).

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

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

template<class Y, class 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() の値を当てにしてはならない。

use_count

long use_count() const; // never throws

Returns: *this も含め、*this所有権を共有している shared_ptr オブジェクトの数。 ただし *this空(empty) の場合は、非負の不定値。

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 : 構文糖)というわけではない。 この変換により dynamic_pointer_castweak_ptr::lockを使う際に、shared_ptr適切に条件式として使うことができる。]

swap

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

Effects: 二つのスマートポインタの内容を入れ替える。

Throws: 無し。

Free Functions

comparison

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

Returns: a.get() == b.get().

Throws: 無し。

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

Returns: a.get() != b.get().

Throws: 無し。

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

Returns: 以下に示すような明示的でない値。

Throws: 無し。

Notes: shared_ptr オブジェクトを連想コンテナのキーとして使えるようにするための演算子。

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

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

swap

template<class 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<class T>
  T * get_pointer(shared_ptr<T> const & p); // never throws

Returns: p.get().

Throws: 無し。

Notes: ジェネリックプログラミングを支援するために提供される。 mem_fn を通して利用する。

static_pointer_cast

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

Requires: static_cast<T*>(r.get()) は整形式(well-formed)でなくてはならない。

Returns: もし r空(empty)である場合、空(empty)の shared_ptr<T>; そうでなければ、 static_cast<T*>(r.get()) のコピーを保持し、 r と所有権を共有する shared_ptr<T> オブジェクト。

Throws: 無し。

Notes: 表面的には次式と等価に見えるが、

shared_ptr<T>(static_cast<T*>(r.get()))

このようにすると、同じオブジェクトを2度削除しようとする事になるため、 結果的に未定義のふるまいとなる。

dynamic_pointer_cast

template<class T, class U>
  shared_ptr<T> dynamic_pointer_cast(shared_ptr<U> const & r);

Requires: dynamic_cast<T*>(r.get()) の式が正しい形であり、 そのふるまいが定義されていなくてはならない。

Returns:

Throws: 無し。

Notes: 表面的には次式と等価に見えるが、

shared_ptr<T>(dynamic_cast<T*>(r.get()))

このようにすると、同じオブジェクトを2度削除しようとする事になるため、 結果的に未定義のふるまいとなる。

operator<<

template<class E, class T, class Y>
    std::basic_ostream<E, T> & operator<< (std::basic_ostream<E, T> & os, shared_ptr<Y> const & p);

Effects: os << p.get();.

Returns: os.

get_deleter

template<class D, class T>
    D * get_deleter(shared_ptr<T> const & p);

Returns: *this が削除子(deleter) d (const/volatile非修飾の型 D のオブジェクト)を所有 している場合は &d を返す。 そうでない場合は 0 を返す。

Example

完全なサンプルプログラムは shared_ptr_example.cpp を参照のこと。 このプログラムは shared_ptr オブジェクトからなる std::vectorstd::set を作成する。

これらのコンテナに 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_ptr は型 T が完全型である必要はない。

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); // p を読みとる

// thread B
shared_ptr<int> p3(p); // OK、多重の読みとりは安全

//--- Example 2 ---

// thread A
p.reset(new int(1912)); // p を変更する

// thread B
p2.reset(); // OK、p2 を変更

//--- Example 3 ---

// thread A
p = p3; // p3 を読みとり、p を変更

// thread B
p3.reset(); // p3 を変更; 未定義、読みとり/変更が同時

//--- Example 4 ---

// thread A
p3 = p2; // p2 を読みとり、p3 を変更

// thread B
// p2 がスコープを抜ける; 未定義、p2 のデストラクタは "書き込み(write)アクセス" をすると思われる

//--- Example 5 ---

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

// thread B
p3.reset(new int(2)); // 未定義、多重の変更

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. 連結リスト方式の実装は、余分なポインタのためのコストに見合うだけの利点が無いからである。 スマートポインタの性能比較 のページを参照せよ。 補足すると、連結リスト方式の実装でスレッドセーフティを実現するには、 非常に大きなコストが生じる。

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

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

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

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

Q. なぜ shared_ptr は計算量の制限( complexity requirements )を明示しないのか?

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

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

A. shared_ptr は unique() な時をのぞいて、所有権を譲渡できない。 なぜなら、いずれは所有権を共有している他の 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 を呼ぶだろう。

その上、 カスタム削除子(deleter)を伴って作成された shared_ptr インスタンスの release() によって返されたポインタを確実に deallocate する事は困難である。

Q. operator->() はコンスト演算子であるのに、 戻り値は内包しているオブジェクトの型( element type )を指す非コンストポインタとなるのはなぜか?

A. 生ポインタを含め、ポインタのコピーは、 一般的にコンスト性を伝播させない。 このようになっているのには小さな根拠がある。 いつでもコンストポインタから非コンストポインタを獲得できることで、 その非コンストポインタを通してオブジェクトに変更を加えることができるようになっているのである。 shared_ptr は "できるだけ生ポインタに近く(as close as possible)、しかし閉塞的ではない(no closer)"


$Date: 2003/09/02 06:19:54 $

Copyright 1999 Greg Colvin and Beman Dawes. Copyright 2002 Darin Adler. Copyright 2002, 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, Kohske Takahashi.
オリジナルの、及びこの著作権表示が全ての複製の中に現れる限り、この文書の 複製、利用、変更、販売そして配布を認める。このドキュメントは「あるがまま」 に提供されており、いかなる明示的、暗黙的保証も行わない。また、 いかなる目的に対しても、その利用が適していることを関知しない。