c++ - O deletador de shared_ptr é armazenado na memória alocada pelo alocador personalizado?




language-lawyer c++17 (2)

Digamos que eu tenha um shared_ptr com um alocador personalizado e um deleter personalizado.

Não consigo encontrar nada no padrão que fale sobre onde o deleter deve ser armazenado: ele não diz que o alocador personalizado será usado para a memória do deleter e não diz que não será.

Isso não é especificado ou estou perdendo alguma coisa?


De std::shared_ptr , temos:

O bloco de controle é um objeto alocado dinamicamente que contém:

  • um ponteiro para o objeto gerenciado ou o próprio objeto gerenciado;
  • o deleter (tipo apagado);
  • o alocador (apagado);
  • o número de shared_ptrs que possuem o objeto gerenciado;
  • o número de fraco_ptrs que se referem ao objeto gerenciado.

E de std::allocate_shared , obtemos:

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

Constrói um objeto do tipo T e o agrupa em um std :: shared_ptr [...] para usar uma alocação para o bloco de controle do ponteiro compartilhado e o objeto T.

Portanto, parece que std::allocate_shared deve alocar o deleter com seu Alloc .

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

1 Os requisitos comuns que se aplicam a todas as make_shared make_shared_default_init , make_shared_default_init , make_shared_default_init e make_shared_default_init , a menos que especificado de outra forma, são descritos abaixo.

[...]

7 Observações: (7.1) - As implementações devem executar não mais que uma alocação de memória. [Nota: Isso fornece eficiência equivalente a um ponteiro inteligente intrusivo. - end note]

[Ênfase toda minha]

Portanto, o padrão está dizendo que std::allocate_shared deve usar Alloc para o bloco de controle.


Eu acredito que isso não é especificado.

Veja como os construtores relevantes são especificados: [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);

Efeitos: shared_ptr um objeto shared_ptr que possui o objeto p deleter d . Quando T não é um tipo de matriz, o primeiro e o segundo construtores ativam shared_from_this com p . O segundo e o quarto construtores devem usar uma cópia de a para alocar memória para uso interno . Se uma exceção for lançada, d(p) será chamado.

Agora, minha interpretação é que, quando a implementação precisa de memória para uso interno, faz isso usando a . Isso não significa que a implementação precise usar essa memória para colocar tudo. Por exemplo, suponha que exista essa implementação estranha:

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, __d);
        else
            // use 'a' to allocate storage for the deleter
    }
};

Esta implementação "usa uma cópia de a para alocar memória para uso interno"? Sim. Ele nunca aloca memória, exceto usando a . Existem muitos problemas com essa implementação ingênua, mas digamos que ela mude para o uso de alocadores, exceto no caso mais simples, no qual o shared_ptr é construído diretamente a partir de um ponteiro e nunca é copiado ou movido ou referenciado e não há outras complicações. A questão é que apenas porque não conseguimos imaginar uma implementação válida não significa que ela possa existir teoricamente. Não estou dizendo que tal implementação possa realmente ser encontrada, apenas que o padrão não parece proibi-lo ativamente.





allocator