1. ホーム
  2. c++

[解決済み] shared_ptr magic :)

2023-02-05 23:40:30

質問

Lidström氏と口論になりました。 :)

Lidström 氏の主張は、構成要素である shared_ptr<Base> p(new Derived); は Base が仮想デストラクタを持つ必要はない、ということです。

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

ダニエル・リドストロム : "The shared_ptr はそれ自身のデストラクタを使用してConcreteインスタンスを削除します。これは、C++ コミュニティでは RAII として知られています。私からのアドバイスは、RAIIについてできる限り学ぶことです。あらゆる状況でRAIIを使用することで、C++のコーディングがとても簡単になります。

Armen Tsirunyan : "私はRAIIについて知っていますし、また最終的には shared_ptr への静的ポインタを持つ場合、pn が 0 になったとき、デストラクタは格納された px を削除するかもしれないことは知っています。 Base への動的なポインタと Derived でない限り Base が仮想デストラクタを持っていない限り、これは未定義の動作になります。間違っていたら訂正してください"。

Daniel Lidström : "The shared_ptr はスタティック型がConcreteであることを知っています。これは、コンストラクタで渡したので知っているのです! 少しマジックのように見えますが、これはデザインによるもので、非常に素晴らしいものだと断言できます。

では、判定してください。もしそうなら)どのようにして shared_ptr を実装することは、多相クラスに仮想デストラクタを要求することなく、どのように可能でしょうか? よろしくお願いします。

どのように解決するのですか?

はい、shared_ptr をそのように実装することは可能です。Boost はそうしていますし、C++11 標準もこの動作を要求しています。柔軟性の追加として、shared_ptr は単なる参照カウンターを超えるものを管理します。いわゆるdeleterは通常、参照カウンターを含む同じメモリブロックに置かれます。しかし、面白いのは、このdeleterの型はshared_ptrの型の一部ではないことです。これは "type erasure" と呼ばれ、基本的に "polymorphic functions" の実装に使われるのと同じテクニックです。 boost::function または std::function で実際のファンクタの型を隠します。この例を動作させるためには、テンプレート化されたコンストラクタが必要です。

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

つまり、これをクラスで使用する場合 BaseDerived ...

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

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

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

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

必要です。 p は変換可能でなければなりません T* . Y は完全な型でなければならない。 という式は delete p は,よく形成されていなければならず,よく定義された振る舞いを有し,例外を投げてはならない。

効果。 を構築する。 shared_ptr オブジェクトを構築する。 その が所有している ポインタ p .

...

また、デストラクタについては(20.7.2.2)。

効果です。 もし *this または他の shared_ptr インスタンス ( use_count() > 1 ) の場合、副作用はありません。 そうでなければ、もし *this がオブジェクトを所有している場合 p とデレタ d , d(p) が呼び出されます。 そうでなければ、もし *this がポインタを所有している場合 p であり、かつ delete p が呼び出されます。

(太字による強調は私です)。