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
emake_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 objetoshared_ptr
que possui o objetop
deleterd
. QuandoT
não é um tipo de matriz, o primeiro e o segundo construtores ativamshared_from_this
comp
. O segundo e o quarto construtores devem usar uma cópia dea
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.