shared_ptrのdeleterは、カスタムアロケーターによって割り当てられたメモリに格納されていますか?


22

私が持っていると言うshared_ptrカスタムアロケータでカスタム削除手段。

削除プログラムを保存する場所について話している標準では何も見つかりません。カスタムアロケータが削除プログラムのメモリに使用されることはなく、そうでないこともありません。

これは指定されていませんか、それとも何か不足していますか?

回答:


11

C ++ 11のutil.smartptr.shared.const / 9:

効果:オブジェクトpと削除者dを所有するshared_ptrオブジェクトを構築します。2番目と4番目のコンストラクタは、aのコピーを使用して、内部で使用するメモリを割り当てます。

2番目と4番目のコンストラクターには次のプロトタイプがあります。

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

最新のドラフトでは、util.smartptr.shared.const / 10は、私たちの目的では同等です。

効果:オブジェクトpと削除者dを所有するshared_ptrオブジェクトを構築します。Tが配列型でない場合、最初と2番目のコンストラクターはpでshared_from_thisを有効にします。2番目と4番目のコンストラクタは、aのコピーを使用して、内部で使用するメモリを割り当てます。例外がスローされると、d(p)が呼び出されます。

したがって、アロケータは、割り当てられたメモリに割り当てる必要がある場合に使用されます。現在の基準と関連する欠陥レポートに基づいて、割り当ては必須ではありませんが、委員会によって想定されます。

  • インタフェースががshared_ptrあり、制御ブロックと、すべてのことはありません実装可能にshared_ptrし、weak_ptrリンクリストに入れているが、実際にはそのような実装ではありません。さらに、たとえばuse_countが共有されていると仮定して、表現が変更されました。

  • 削除可能なのは、構成可能オブジェクトのみを移動するためです。したがって、に複数のコピーを持つことはできませんshared_ptr

Deleterを特別に設計された場所に配置しshared_ptr、Special shared_ptrが削除されたときにそれを移動する実装を想像できます。実装は適合しているように見えますが、特に使用回数に制御ブロックが必要になる可能性があるため、これも奇妙です(おそらく、使用回数で同じことを行うことは可能ですが、さらに奇妙です)。

私が見つけた関連のDR:5455752434、(すべての実装が制御ブロックを使用していることを確認幾分それを強制そのマルチスレッド制約を暗示しているように見える)2802デリータのみ構築可能に移動することを必要とする(したがって、実装場所を防ぎ削除者はいくつかshared_ptrのの間でコピーされます)。


2
「内部で使用するためにメモリを割り当てるには」内部で使用するためにメモリを割り当てることを実装が行わない場合はどうなりますか?メンバーが利用できます。
LF

1
@LFできません。インターフェースはそれを許可していません。
AProgrammer

理論的には、「小さな削除機能の最適化」をまだ使用できますよね?
LF

奇妙なのは、同じメモリa割り当て解除するために同じアロケータ(のコピー)を使用することについて何も見つからないことです。これは、のそのコピーの一部のストレージを意味しますa。[util.smartptr.shared.dest]にはそれに関する情報はありません。
Daniel Langr

1
@DanielsaysreinstateMonica、私はutil.smartptr.shared / 1にあるかどうか疑問に思います: "shared_ptrクラステンプレートは、通常newを介して取得されるポインタを格納します。shared_ptrは共有所有権のセマンティクスを実装します。最後に残ったポインタの所有者はオブジェクトを破棄する責任があります。または、保存されているポインタに関連付けられているリソースを解放する。」格納されたポインタに関連付けられたリソース解放は、そのためのものではありません。ただし、最後の弱いポインターが削除されるまで、制御ブロックも存続するはずです。
AProgrammer

4

std :: shared_ptrから:

制御ブロックは、以下を保持する動的に割り当てられたオブジェクトです。

  • 管理オブジェクトへのポインタまたは管理オブジェクト自体。
  • 削除者(タイプ消去)
  • アロケータ(型消去)。
  • 管理対象オブジェクトを所有するshared_ptrsの数。
  • 管理対象オブジェクトを参照するweak_ptrsの数。

そしてstd :: allocate_sharedから次のようになります:

template< class T, class Alloc, class... Args >
shared_ptr<T> allocate_shared( const Alloc& alloc, Args&&... args );

共有ポインターの制御ブロックとTオブジェクトの両方に1つの割り当てを使用するために、T型のオブジェクトを作成し、それをstd :: shared_ptr [...]でラップします。

だから、のように見えるのstd :: allocate_shared割り当てる必要がありますdeleterあなたにAlloc

編集:n4810§20.11.3.6 から[util.smartptr.shared.create]

1全てに適用される共通の要件make_sharedallocate_sharedmake_shared_default_init、及びallocate_shared_default_init過負荷、特に断りのない限り、以下に記載されています。

[...]

7備考:(7.1)— 実装は、メモリ割り当てを1つだけ実行する必要があります。[注:これにより、侵入型スマートポインタと同等の効率が得られます。—エンドノート]

[すべてのエンファシス]

したがって、標準では、それstd::allocate_shared Alloc制御ブロックに使用する必要があると述べています。


1
cppreferenceは規範的なテキストではありません。優れたリソースですが、必ずしも言語弁護士の質問に役立つわけではありません。
StoryTeller-Unslander Monica

@ StoryTeller-UnslanderMonica完全に同意-最新の標準を調べても何も見つからなかったため、cppreferenceを使用しました。
Paul Evans

@ PaulEvans、eel.is
c

n4810回答を見つけて更新しました。
ポールエヴァンス

1
ただし、これはについての話make_sharedであり、コンストラクタ自体についての話ではありません。それでも、小さな削除者のメンバーを使用できます。
LF

3

これは不特定だと思います。

関連するコンストラクタの仕様は次のとおりです:[util.smartptr.shared.const] / 10

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

効果:shared_­ptrオブジェクトpと削除者を所有するオブジェクトを構築しますdTが配列型でない場合、最初のコンストラクタと2番目のコンストラクタはで有効にshared_­from_­thisなりpます。2番目と4番目のコンストラクタはのコピーを使用して、a内部で使用するメモリを割り当てます。例外がスローされた場合、d(p)が呼び出されます。

今、私の解釈は、実装が内部使用のためにメモリを必要とするとき、それを使用することによってそれをするということaです。実装がすべてを配置するためにこのメモリを使用する必要があるという意味ではありません。たとえば、次の奇妙な実装があるとします。

template <typename T>
class shared_ptr : /* ... */ {
    // ...
    std::aligned_storage<16> _Small_deleter;
    // ...
public:
    // ...
    template <class _D, class _A>
    shared_ptr(nullptr_t, _D __d, _A __a) // for example
        : _Allocator_base{__a}
    {
        if constexpr (sizeof(_D) <= 16)
            _Construct_at(&_Small_deleter, std::move(__d));
        else
            // use 'a' to allocate storage for the deleter
    }
// ...
};

この実装は「のコピーを使用して、a内部使用のためにメモリを割り当てますか?」はい、そうです。を使用しない限り、メモリは割り当てられませんa。この素朴な実装には多くの問題がありますが、それがアロケーターの使用に切り替わったとしましょう。ただし、shared_ptrはポインターから直接作成され、コピーや移動などの参照は行われず、他の複雑さはありません。重要なのは、有効な実装がそれ自体では理論的に存在できないことを証明できないとは想像できないからです。このような実装が実際に現実の世界で見られると言っているのではなく、標準が積極的にそれを禁止していないように見えるだけです。


shared_ptr小さな型のIMO は、スタックにメモリを割り当てます。そのため、標準要件を満たしていません
bartop

1
@bartopスタックにメモリを「割り当て」ません。_Smaller_deleterは、無条件に、shared_ptrの表現の一部です。このスペースでコンストラクターを呼び出すことは、何かを割り当てることを意味しません。それ以外の場合、制御ブロックへのポインタを保持していても、「メモリの割り当て」としてカウントされますよね?:-)
LF

しかし、削除機能がコピー可能である必要はないので、これはどのように機能しますか?
Nicol Bolas、

@NicolBolas Umm ...を使用してstd::move(__d)allocateコピーが必要なときにフォールバックします。
LF
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.