7

構築中に (明確に定義された動作で) ポインターを削除できる限り、 を使用して不完全な型へのポインターを格納できることは広く 知られています。たとえば、PIMPL 手法は次のとおりです。shared_ptrshared_ptr

struct interface
{
    interface();                 // out-of-line definition required
    ~interface() = default;   // public inline member, even if implicitly defined
    void foo();
private:
    struct impl;                 // incomplete type
    std::shared_ptr<impl> pimpl; // pointer to incomplete type
};

[main.cpp]

int main()
{
    interface i;
    i.foo();
}

[インターフェイス.cpp]

struct interface::impl
{
    void foo()
    {
        std::cout << "woof!\n";
    }
};

interface::interface()
    : pimpl( new impl ) // `delete impl` is well-formed at this point
{}

void interface::foo()
{
    pimpl->foo();
}

これは、「削除オブジェクト」「所有者オブジェクト」(*) として機能し、 の構築中に作成されshared_ptrpimpl( new impl )型消去後に に格納されshared_ptrます。この「所有者オブジェクト」は、指定されたオブジェクトを破棄するために後で使用されます。そのため、 のインラインデストラクタを提供しても安全ですinterface

質問:規格は安全であることをどこで保証していますか?

(*) 標準の点ではデリータではありません。以下を参照してください。ただし、カスタムのデリータを呼び出すか、削除式を呼び出します。このオブジェクトは通常、ブックキーピング オブジェクトの一部として格納され、型の消去を適用し、仮想関数でカスタムの削除子/削除式を呼び出します。この時点で、削除式も適切な形式になっているはずです。


github リポジトリの最新ドラフト (94c8fc71、N3797 を改訂) を参照すると、[util.smartptr.shared.const]

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

3 必須:pに変換可能である必要がありますT*Y完全型になります。式delete p は整形式でなければならず、動作が明確に定義されていなければならず、例外をスローしてはなりません。

shared_ptr4 効果:ポインターを所有するオブジェクトを構築しますp

5 事後条件: use_count() == 1 && get() == p.

6 スロー: bad_alloc、またはメモリ以外のリソースを取得できなかった場合の実装定義の例外。

注:このshared_ptr ctor の場合、 は deleter を所有する必要はありませんdeleterによって、標準はカスタム deleterを意味するようです。たとえば、構築中に追加のパラメーターとして提供します (または、コピー割り当てなどによりshared_ptr、別のものを取得/共有します)。shared_ptr参照してください ([util.smartptr.shared.const]/9 も参照)。実装 (boost、libstdc++、MSVC、およびすべての正気な実装だと思います) は、常に「所有者オブジェクト」を格納します。

deleterカスタム deleterであるため、カスタム deleter がない場合、 のデストラクタは(delete-expression)shared_ptrに関して定義されます。delete

[util.smartptr.shared.dest]

~shared_ptr();

1 効果:

  • *thisshared_ptrであるか、別のインスタンス ( )と所有権を共有している場合use_count() > 1、副作用はありません。
  • それ以外の場合、がオブジェクトを*this 所有しpdeleter が呼び出される場合。dd(p)
  • それ以外の場合は、ポインターを*this 所有し、呼び出されます。pdelete p

意図shared_ptrは、 dtorのスコープ内で削除式が不適切な形式であるか、UB を呼び出す場合でも、格納されたポインターを正しく削除するために実装が必要であるということであると仮定します。(delete-expression は整形式であり、ctor で明確に定義された動作を持っている必要があります。) したがって、問題は次のとおりです。

質問:これはどこで必要ですか?

(または、私はあまりにもうるさいので、実装が「所有者オブジェクト」を使用する必要があることはどういうわけか明らかですか?)

4

1 に答える 1

4

質問: これはどこで必要ですか?

それが要求されなかった場合、デストラクタは未定義の動作をし、標準は未定義の動作を要求する習慣がありません:-)

コンストラクタの前提条件を満たしている場合、デストラクタは未定義の動作を呼び出しません。実装がそれを保証する方法は指定されていませんが、それが正しく行われると想定することができ、その方法を知る必要はありません。実装が正しいことを行うことが期待されていない場合、デストラクタには前提条件があります。

(または、私はあまりにもうるさいので、実装が「所有者オブジェクト」を使用する必要があることはどういうわけか明らかですか?)

はい、ポインターを所有するために作成された追加のオブジェクトが必要です。参照カウント (または他の簿記データ) は特定のshared_ptrインスタンスの一部ではなくヒープ上にある必要があるためです。そうです、所有者オブジェクトと呼ぶことができるポインターを所有する追加のオブジェクトがあります。ユーザーがデリータを提供しない場合、その所有者オブジェクトは単に を呼び出しますdelete。例えば:

template<typename T>
struct SpOwner {
  long count;
  long weak_count;
  T* ptr;
  virtual void dispose() { delete ptr; }
  // ...
};

template<typename T, typename Del>
struct SpOwnerWithDeleter : SpOwner<T> {
  Del del;
  virtual void dispose() { del(this->ptr); }
  // ...
};

現在、shared_ptrには がSpOwner*あり、カウントがゼロになると、オブジェクトの構築方法に応じて、デリータを呼び出すdispose()か呼び出す仮想関数が呼び出されます。anまたは andeleteを構築するかどうかの決定は、 が構築されたときに行われ、その型はが破棄されたときにも同じであるため、所有されているポインターを破棄する必要がある場合は、正しいことを行います。SpOwnerSpOwnerWithDeletershared_ptrshared_ptr

于 2013-11-07T21:06:00.310 に答える