web-dev-qa-db-ja.com

shared_ptr magic :)

Lidström氏と私は引数を持っていました :)

Lidström氏の主張は、コンストラクトshared_ptr<Base> p(new Derived);がBaseに仮想デストラクタを必要としないことです:

Armen Tsirunyan: "本当に?shared_ptrは正しくクリーンアップされますか?この場合、その効果がどのようになるかを示していただけませんか実装されましたか?」

DanielLidström: "shared_ptrは独自のデストラクタを使用して具象インスタンスを削除します。これはC++コミュニティではRAIIと呼ばれています。私のアドバイスは、RAIIについてできる限りのことを学ぶことです。あらゆる状況でRAIIを使用すると、C++コーディングが非常に簡単になります。」

Armen Tsirunyan: "私はRAIIについて知っています。また、最終的にshared_ptrデストラクタがpnのときに保存されたpxを削除する可能性があることも知っていますただし、pxにBaseへの静的型ポインターとDerivedへの動的型ポインターがある場合、Baseに仮想デストラクタがない限り、これは未定義の動作になります。私が間違っているなら私に」

DanielLidström: "-shared_ptrは静的型がコンクリートであることを知っています。コンストラクタで渡したので、これは知っています!少し魔法のようですが、それは仕様によるものであり、非常に素晴らしいものです。」

だから、私たちを判断してください。ポリモーフィッククラスに仮想デストラクタを必要とせずにshared_ptrを実装する(可能な場合)にはどうすればよいですか?前もって感謝します

85
Armen Tsirunyan

はい、その方法でshared_ptrを実装することは可能です。 Boostはこれを行い、C++ 11標準もこの動作を必要とします。追加の柔軟性として、shared_ptrは単なる参照カウンター以上のものを管理します。いわゆる削除機能は通常、参照カウンタも含む同じメモリブロックに配置されます。しかし、楽しい部分は、この削除機能のタイプがshared_ptrタイプの一部ではないことです。これは「型消去」と呼ばれ、基本的に「ポリモーフィック関数」boost :: functionまたはstd :: functionを実装して実際のファンクターの型を非表示にするために使用される手法と同じです。サンプルを機能させるには、テンプレート化されたコンストラクタが必要です。

_template<class T>
class shared_ptr
{
public:
   ...
   template<class Y>
   explicit shared_ptr(Y* p);
   ...
};
_

したがって、これをクラスBaseおよびDerivedで使用すると...

_class Base {};
class Derived : public Base {};

int main() {
   shared_ptr<Base> sp (new Derived);
}
_

... Y = Derivedのテンプレート化されたコンストラクターは、shared_ptrオブジェクトの構築に使用されます。したがって、コンストラクターは適切な削除オブジェクトと参照カウンターを作成し、この制御ブロックへのポインターをデータメンバーとして格納する機会があります。参照カウンターがゼロに達すると、以前に作成されたDerived-awareの削除機能を使用してオブジェクトが破棄されます。

C++ 11標準では、このコンストラクタについて次のように述べています(20.7.2.2.1)。

要件:pは_T*_に変換可能でなければなりません。 Yは完全な型でなければなりません。 式_delete p_は適切に形成され、明確に定義された動作を備え、例外をスローしません。

効果:_shared_ptr_オブジェクトを作成しますthatownsポインタp

デストラクタ(20.7.2.2.2)の場合:

効果:_*this_がemptyであるか、所有権を共有している場合別の_shared_ptr_インスタンス(use_count() > 1)、副作用はありません。それ以外の場合、_*this_がオブジェクトpおよび削除機能dを所有している場合、d(p)が呼び出されます。 それ以外の場合、_*this_がポインターpを所有し、_delete p_が呼び出された場合

(太字のフォントを使用することに重点を置いています)。

70
sellibitze

Shared_ptrが作成されると、deleterオブジェクトがそれ自体の内部に格納されます。このオブジェクトは、shared_ptrが指定されたリソースを解放しようとするときに呼び出されます。構築時点でリソースを破棄する方法を知っているので、不完全な型でshared_ptrを使用できます。 shared_ptrを作成した人は、正しい削除者をそこに保存しました。

たとえば、カスタムの削除機能を作成できます。

void DeleteDerived(Derived* d) { delete d; } // EDIT: no conversion needed.

shared_ptr<Base> p(new Derived, DeleteDerived);

pはDeleteDerivedを呼び出して、ポイントされたオブジェクトを破棄します。実装はこれを自動的に行います。

28
ybungalobill

単に、

shared_ptrは、Baseのデストラクタではなく、指定されたオブジェクトのデストラクタを常に使用するコンストラクタによって作成される特別な削除機能を使用します。これは、テンプレートメタプログラミングで少し処理されますが、機能します。

そんな感じ

template<typename SomeType>
shared_ptr(SomeType *p)
{
   this->destroyer = destroyer_function<SomeType>(p);
   ...
}
16
Artyom