私は共有ポインタの使用を考えていましたが、自分で実装する方法を知っています-したくないので、std::tr1::shared_ptr
を試してみて、いくつか質問があります...
参照カウントはどのように実装されていますか?二重にリンクされたリストを使用していますか? (ところで、私はすでにググっていますが、信頼できるものを見つけることができません。)
std::tr1::shared_ptr
を使用する際の落とし穴はありますか?
shared_ptr
は、参照カウンターと、初期化時に指定されたオブジェクトのタイプによって推定される削除機能の伝達を管理する必要があります。
shared_ptr
クラスは通常、2つのメンバーをホストします。T*
(operator->
によって返され、operator*
で逆参照されます)とaux*
で、aux
は以下を含む内部抽象クラスです。
virtual destroy()=0;
そのようなaux
クラス(実際の名前は実装によって異なります)は、テンプレート化されたクラス(明示的なコンストラクターによって指定された型でパラメーター化され、たとえばU
から派生したT
)のファミリーによって派生します。
T*
と同じですが、実際のタイプ:これは、T
が派生階層で複数のU
を持つT
のベースであるすべてのケースを適切に管理するために必要です)deletor
オブジェクトのコピー(または、削除deletor
を実行するだけのデフォルトのp
、ここでp
は上記のU*
です)簡略化したスケッチは次のようになります。
template<class T>
class shared_ptr
{
struct aux
{
unsigned count;
aux() :count(1) {}
virtual void destroy()=0;
virtual ~aux() {} //must be polymorphic
};
template<class U, class Deleter>
struct auximpl: public aux
{
U* p;
Deleter d;
auximpl(U* pu, Deleter x) :p(pu), d(x) {}
virtual void destroy() { d(p); }
};
template<class U>
struct default_deleter
{
void operator()(U* p) const { delete p; }
};
aux* pa;
T* pt;
void inc() { if(pa) interlocked_inc(pa->count); }
void dec()
{
if(pa && !interlocked_dec(pa->count))
{ pa->destroy(); delete pa; }
}
public:
shared_ptr() :pa(), pt() {}
template<class U, class Deleter>
shared_ptr(U* pu, Deleter d) :pa(new auximpl<U,Deleter>(pu,d)), pt(pu) {}
template<class U>
explicit shared_ptr(U* pu) :pa(new auximpl<U,default_deleter<U> >(pu,default_deleter<U>())), pt(pu) {}
shared_ptr(const shared_ptr& s) :pa(s.pa), pt(s.pt) { inc(); }
template<class U>
shared_ptr(const shared_ptr<U>& s) :pa(s.pa), pt(s.pt) { inc(); }
~shared_ptr() { dec(); }
shared_ptr& operator=(const shared_ptr& s)
{
if(this!=&s)
{
dec();
pa = s.pa; pt=s.pt;
inc();
}
return *this;
}
T* operator->() const { return pt; }
T& operator*() const { return *pt; }
};
weak_ptr
の相互運用性が必要な場合は、aux
に2番目のカウンター(weak_count
)が必要です(weak_ptr
によってインクリメント/デクリメントされます)。また、delete pa
は、両方のカウンターがゼロに達したときにのみ発生する必要があります。
参照カウントはどのように実装されますか?
ポリシーベースのクラス設計 を使用して、スマートポインターの実装を分解できます。1、に:
ストレージポリシー
所有権ポリシー
変換ポリシー
チェックポリシー
テンプレートパラメータとして含まれています。一般的な所有権戦略には、ディープコピー、参照カウント、参照リンク、破壊的コピーが含まれます。
参照カウントは、(所有している)スマートポインターの数を追跡します2)同じオブジェクト。数値がゼロになると、指示先オブジェクトが削除されます3。実際のカウンターは次のようになります。
Pointeeオブジェクト自体に含まれている、侵入参照カウント。欠点は、オブジェクトを数える機能を備えたアプリオリに構築する必要があることです。
最後に、あなたの質問のメソッド、二重にリンクされたリストを使用した参照カウントは、参照リンクと呼ばれ、それは次のとおりです。
... [1] 1つの指示先オブジェクトを指すスマートポインターオブジェクトの実際の数は実際には必要ないという観察に依存しています。そのカウントがゼロになるときを検出する必要があるだけです。これは、「所有権リスト」を保持するという考えにつながります。
参照カウントよりも参照リンクの利点は、前者が余分なフリーストアを使用しないことです。これにより、信頼性が向上します。参照リンクされたスマートポインターの作成は失敗しません。不利な点は、参照リンクが簿記のために多くのメモリを必要とすることです(3つのポインターと1つのポインターと1つの整数のみ)。また、参照カウントは少し高速である必要があります。スマートポインタをコピーする場合、必要なのは間接と増分だけです。リスト管理はもう少し複雑です。結論として、無料のストアが不足している場合にのみ、参照リンクを使用する必要があります。それ以外の場合は、参照カウントを優先します。
(_
std::shared_ptr
_)は二重にリンクされたリストを使用していますか?
C++標準で見つけることができたのは次のとおりです。
20.7.2.2.6 shared_ptrの作成
...
7。 [注:これらの関数は通常、sizeof(T)
より多くのメモリを割り当てて、参照カウントなどの内部簿記構造を考慮に入れます。 —エンドノート]
私の意見では、実際のカウントが含まれていないため、二重にリンクされたリストは除外されます。
_
std::shared_ptr
_を使用する際の落とし穴はありますか?
カウントまたはリンクのいずれかの参照管理は、循環参照として知られるリソースリークの犠牲者です。オブジェクトBへのスマートポインタを保持するオブジェクトAがあるとします。また、オブジェクトBはAへのスマートポインタを保持します。これら2つのオブジェクトは循環参照を形成します。それらのいずれも使用しなくなったとしても、お互いを使用します。参照管理戦略はこのような循環参照を検出できず、2つのオブジェクトは永久に割り当てられたままになります。
_shared_ptr
_の実装は参照カウントを使用するため、循環参照が潜在的に問題になります。循環の_shared_ptr
_チェーンは、参照の1つが_weak_ptr
_になるようにコードを変更することで解除できます。これは、共有ポインターとウィークポインターの間に値を割り当てることによって行われますが、ウィークポインターは参照カウントに影響しません。オブジェクトを指す唯一のポインターが弱い場合、オブジェクトは破棄されます。
1.ポリシーとして定式化されている場合、複数の実装を持つ各設計機能。
2. new
で割り当てられたオブジェクトを指すポインターと同様のスマートポインターは、そのオブジェクトを指すだけでなく、そのオブジェクトの破棄と、それが占有するメモリの解放も行います。
3.他の生のポインターが使用されていないか、ポインターをポイントしていない場合、問題は発生しません。
[1]現代のC++デザイン:一般的なプログラミングとデザインパターンを適用。 Andrei Alexandrescu、2001年2月1日
すべての詳細を確認したい場合は、boost shared_ptr
の実装を確認できます。
https://github.com/boostorg/smart_ptr
参照カウントは通常、カウンターとプラットフォーム固有のアトミックインクリメント/デクリメント命令またはミューテックスを使用した明示的なロックで実装されているようです( detail名前空間 のatomic_count_*.hpp
ファイルを参照)。
std::tr1::shared_ptr
を使用する際の落とし穴はありますか?
はい、共有メモリポインタにサイクルを作成した場合、スマートポインタによって管理されているメモリは、ポインタへの参照が残っているため、最後のポインタがスコープから外れたときにリサイクルされません(つまり、サイクルによって参照カウントが発生します)ゼロに下がらないように)。
例えば:
struct A
{
std::shared_ptr<A> ptr;
};
std::shared_ptr<A> shrd_ptr_1 = std::make_shared(A());
std::shared_ptr<B> shrd_ptr_2 = std::make_shared(A());
shrd_ptr_1->ptr = shrd_ptr_2;
shrd_ptr_2->ptr = shrd_ptr_1;
現在、shrd_ptr_1
とshrd_ptr_2
がスコープ外になったとしても、それぞれのptr
メンバーが互いにポイントしているため、それらが管理しているメモリは解放されません。これはそのようなメモリサイクルの非常に単純な例ですが、これらの種類のポインタを規律なしで使用すると、はるかに悪質で追跡が困難な方法で発生する可能性があります。たとえば、各next
ポインタがstd::shared_ptr
である循環リンクリストを実装しようとすると、あまり注意しないと問題が発生する可能性がある場所を確認できます。