Il deleter di shared_ptr è archiviato nella memoria allocata dall'allocatore personalizzato?


22

Supponiamo che io abbia un shared_ptrcon un allocatore personalizzato e un deleter personalizzato.

Non riesco a trovare nulla nello standard che parli di dove dovrebbe essere archiviato il deleter: non dice che l'allocatore personalizzato verrà utilizzato per la memoria del deleter e non dice che non lo sarà.

Questo non è specificato o mi sto perdendo qualcosa?

Risposte:


11

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

Effetti: crea un oggetto shared_ptr che possiede l'oggetto pe il deleter d. Il secondo e il quarto costruttore devono utilizzare una copia di a per allocare memoria per uso interno.

Il secondo e il quarto costruttore hanno questi prototipi:

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

Nell'ultima bozza, util.smartptr.shared.const / 10 è equivalente al nostro scopo:

Effetti: crea un oggetto shared_ptr che possiede l'oggetto pe il deleter d. Quando T non è un tipo di array, il primo e il secondo costruttore abilitano shared_from_this con p. Il secondo e il quarto costruttore devono utilizzare una copia di a per allocare memoria per uso interno. Se viene generata un'eccezione, viene chiamato d (p).

Quindi l'allocatore viene utilizzato se è necessario allocarlo nella memoria allocata. Sulla base dello standard attuale e delle relative relazioni sui difetti, l'assegnazione non è obbligatoria ma è assunta dal comitato.

  • Sebbene l'interfaccia di shared_ptrconsenta un'implementazione in cui non esiste mai un blocco di controllo e tutti shared_ptre weak_ptrsono inseriti in un elenco collegato, in pratica non esiste tale implementazione. Inoltre, il testo è stato modificato ipotizzando, ad esempio, che use_countsia condiviso.

  • Il deleter è tenuto a muoversi solo costruibile. Pertanto, non è possibile avere più copie nel file shared_ptr.

Si può immaginare un'implementazione che inserisce il deleter in uno appositamente progettato shared_ptre lo sposta quando lo speciale shared_ptrviene eliminato. Sebbene l'implementazione appaia conforme, è anche strano, soprattutto perché potrebbe essere necessario un blocco di controllo per il conteggio degli usi (è forse possibile ma anche più strano fare la stessa cosa con il conteggio degli usi).

DR rilevanti che ho trovato: 545 , 575 , 2434 (che riconoscono che tutte le implementazioni utilizzano un blocco di controllo e sembrano implicare che i vincoli multi-thread lo obbligano in qualche modo), 2802 (che richiede che il deleter si muova solo costruibile e quindi impedisce l'implementazione in cui il deleter viene copiato tra diversi shared_ptr).


2
"allocare memoria per uso interno" E se l'implementazione non allocasse memoria per uso interno all'inizio? Può usare un membro.
LF

1
@LF Non può, l'interfaccia non lo consente.
Approgrammatore

Teoricamente, può ancora usare una sorta di "piccola ottimizzazione del deleter", giusto?
LF

La cosa strana è che non riesco a trovare nulla sull'uso dello stesso allocatore (copia di a) per deallocare quella memoria. Il che implicherebbe un po 'di archiviazione di quella copia di a. Non ci sono informazioni al riguardo in [util.smartptr.shared.dest].
Daniel Langr,

1
@DanielsaysreinstateMonica, mi chiedo se in util.smartptr.shared / 1: "Il modello di classe shared_ptr memorizza un puntatore, solitamente ottenuto tramite new. Shared_ptr implementa la semantica della proprietà condivisa; l'ultimo proprietario rimanente del puntatore è responsabile della distruzione dell'oggetto, o altrimenti liberando le risorse associate al puntatore memorizzato. " il rilascio delle risorse associate al puntatore memorizzato non è previsto per questo. Ma anche il blocco di controllo dovrebbe sopravvivere fino all'eliminazione dell'ultimo puntatore debole.
Approgrammatore

4

Da std :: shared_ptr abbiamo:

Il blocco di controllo è un oggetto allocato dinamicamente che contiene:

  • un puntatore all'oggetto gestito o l'oggetto gestito stesso;
  • il deleter (cancellato dal tipo);
  • l'allocatore (cancellato dal tipo);
  • il numero di shared_ptrs che possiedono l'oggetto gestito;
  • il numero di weak_ptrs che si riferiscono all'oggetto gestito.

E da std :: allocate_shared otteniamo:

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

Costruisce un oggetto di tipo T e lo avvolge in uno std :: shared_ptr [...] al fine di utilizzare un'allocazione sia per il blocco di controllo del puntatore condiviso che per l'oggetto T.

Quindi sembra che std :: allocate_shared dovrebbe allocare il deletercon il tuo Alloc.

EDIT: E da n4810§20.11.3.6 Creazione [util.smartptr.shared.create]

1 I requisiti comuni che si applicano a tutti make_shared, allocate_shared, make_shared_default_init, e allocate_shared_default_initsovraccarichi, se non diversamente specificato, sono descritti di seguito.

[...]

7 Note: (7.1) - Le implementazioni non devono eseguire più di un'allocazione di memoria. [Nota: questo fornisce efficienza equivalente a un puntatore intelligente invadente. —Endola nota]

[Enfasi tutta mia]

Quindi lo standard dice che std::allocate_shared dovrebbe essere usato Allocper il blocco di controllo.


1
Mi dispiace che cppreference non sia un testo normativo. È una grande risorsa, ma non necessariamente per le domande sull'avvocato linguista .
StoryTeller - Unslander Monica,

@ StoryTeller-UnslanderMonica Totalmente d'accordo - ho guardato attraverso gli standard più recenti e non sono riuscito a trovare nulla, quindi sono andato con cppreference.
Paul Evans,


n4810Risposta trovata e aggiornata.
Paul Evans,

1
Tuttavia, si tratta di questo make_shared, non dei costruttori stessi. Tuttavia, posso usare un membro per piccoli deleter.
LF

3

Credo che questo non sia specificato.

Ecco le specifiche dei costruttori rilevanti: [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);

Effetti: costruisce un shared_­ptroggetto che possiede l'oggetto pe il deleter d. Quando Tnon è un tipo di array, il primo e il secondo costruttore abilitano shared_­from_­thiscon p. Il secondo e il quarto costruttore devono utilizzare una copia di aper allocare memoria per uso interno . Se viene generata un'eccezione, d(p)viene chiamata.

Ora, la mia interpretazione è che quando l'implementazione ha bisogno di memoria per uso interno, lo fa usando a. Ciò non significa che l'implementazione debba usare questa memoria per posizionare tutto. Ad esempio, supponiamo che ci sia questa strana implementazione:

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
    }
// ...
};

Questa implementazione "utilizza una copia di aper allocare memoria per uso interno"? Sì, lo fa. Non alloca mai la memoria se non usando a. Ci sono molti problemi con questa implementazione ingenua, ma diciamo che passa all'utilizzo degli allocatori in tutti tranne il caso più semplice in cui shared_ptrviene costruito direttamente da un puntatore e non viene mai copiato o spostato o altrimenti referenziato e non ci sono altre complicazioni. Il punto è, solo perché non riusciamo a immaginare un'implementazione valida non prova da sola che non può teoricamente esistere. Non sto dicendo che una simile implementazione possa effettivamente essere trovata nel mondo reale, solo che lo standard non sembra proibirla attivamente.


IMO shared_ptrper piccoli tipi alloca memoria nello stack. E quindi non soddisfa i requisiti standard
bartop

1
@bartop Non "alloca" alcuna memoria nello stack. _Smaller_deleter è incondizionatamente una parte della rappresentazione di shared_ptr. Chiamare un costruttore su questo spazio non significa assegnare nulla. Altrimenti, anche tenere un puntatore al blocco di controllo conta come "allocazione di memoria", giusto? :-)
LF

Ma il deleter non deve essere copiabile, quindi come funzionerebbe?
Nicol Bolas,

@NicolBolas Umm ... Utilizzare std::move(__d)e tornare a allocatequando è richiesta una copia.
LF
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.