web-dev-qa-db-ja.com

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

shared_ptrカスタムアロケーターおよびカスタムデリーター。

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

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

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_ptrweak_ptrがリンクリストに配置される実装を許可しますが、実際にはそのような実装はありません。さらに、たとえば、use_countが共有されていると想定して、表現が変更されました。

  • 削除できるのは、構成可能なものだけを移動するためです。したがって、shared_ptrに複数のコピーを含めることはできません。

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

私が見つけた関連DR: 5455752434 (すべての実装が制御ブロックを使用していることを認め、マルチスレッドを示唆しているようです制約はそれをいくらか義務付けます)、 2802 (これは、削除機能が構成可能にのみ移動する必要があるため、削除機能が複数のshared_ptr間でコピーされる実装を防ぎます)。

11
AProgrammer

std :: shared_ptr から:

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

  • 管理オブジェクトへのポインタまたは管理オブジェクト自体。
  • deleter(type-erased);
  • アロケータ(型消去)。
  • 管理対象オブジェクトを所有する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のオブジェクトを作成し、それをstd :: shared_ptr [...]でラップして、共有ポインターとTオブジェクトの制御ブロックの両方に1つの割り当てを使用します。

したがって、 std :: allocate_shareddeleterAllocで割り当てる必要があります。

編集:そしてn4810から§20.11.3.6作成[util.smartptr.shared.create]

1すべてのmake_sharedallocate_sharedmake_shared_default_init、およびallocate_shared_default_initのオーバーロードに適用される共通の要件は、特に明記されていない限り、以下で説明されています。

[...]

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

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

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

4
Paul Evans

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

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

_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);
_

効果:オブジェクトpおよび削除機能dを所有する_shared_­ptr_オブジェクトを構築します。 Tが配列型ではない場合、最初と2番目のコンストラクターはpで_shared_­from_­this_を有効にします。 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_がポインターから直接構築され、コピーまたは移動されない、または参照されない最も単純な場合を除いて、アロケーターの使用に切り替わるとします。他の合併症はありません。重要なのは、有効な実装がそれ自体では理論的に存在できないことを証明できないとは想像できないからです。そのような実装が実際に現実の世界で見られると言っているのではなく、標準が積極的にそれを禁止していないように見えるだけです。

3
L. F.