O dtor do shared_ptr requer o uso de um “deleter”?
Está amplamente conhecido que você pode usar umshared_ptr
armazenar um ponteiro para um tipo incompleto, desde que o ponteiro possa ser excluído (com comportamento bem definido) durante a construção doshared_ptr
. Por exemplo, a técnica PIMPL:
struct interface
{
interface(); // out-of-line definition required
~interface() = default; // public inline member, even if implicitly defined
void foo();
private:
struct impl; // incomplete type
std::shared_ptr<impl> pimpl; // pointer to incomplete type
};
[main.cpp]
int main()
{
interface i;
i.foo();
}
[interface.cpp]
struct interface::impl
{
void foo()
{
std::cout << "woof!\n";
}
};
interface::interface()
: pimpl( new impl ) // `delete impl` is well-formed at this point
{}
void interface::foo()
{
pimpl->foo();
}
Isso funciona como um"objeto deleter" "objeto proprietário" (*) é criado durante a construção doshared_ptr
empimpl( new impl )
e armazenado após o apagamento do tipo dentro doshared_ptr
. Este "objeto proprietário" é usado mais tarde para destruir o objeto apontado. É por isso que deve ser seguro fornecer umna linha destruidor deinterface
.
Questão: Onde o padrão garante que é seguro?
(*) Não é um delírio em termos do Padrão, veja abaixo, mas ele também chama o delírio customizado ou invoca a expressão de exclusão. Esse objeto é normalmente armazenado como parte do objeto de escrituração, aplicando o apagamento de tipos e invocando o deleter / delete-expression personalizado em uma função virtual. Neste ponto, a expressão de exclusão deve ser bem formada também.
Referindo-se ao último rascunho no repositório do github (94c8fc71, revisando o N3797), [util.smartptr.shared.const]
template<class Y> explicit shared_ptr(Y* p);
3 requer:p
deve ser conversível paraT*
. Y
deve ser um tipo completo. A expressãodelete p
deve ser bem formado, deve ter um comportamento bem definido, e não deve lançar exceções.
4 efeitos: constrói umshared_ptr
objeto que possui o ponteirop
.
5 pós-condições:use_count() == 1 && get() == p
.
6 lances:bad_alloc
ou uma exceção definida pela implementação quando um recurso diferente da memória não pôde ser obtido.
Nota: Para este ctor,shared_ptr
não é necessário possuir um delter. Pordeleter, o Padrão parece significardelémetro personalizado, como você fornece durante a construção como um parâmetro adicional (ou oshared_ptr
adquire / compartilha um do outroshared_ptr
, por exemplo. através de atribuição de cópia). Veja também (veja também [util.smartptr.shared.const] / 9). As implementações (boost, libstdc ++, MSVC e eu acho que toda implementação sã) sempre armazenam um "objeto proprietário".
Como umdeleter é umdelémetro personalizado, o destruidor deshared_ptr
é definido em termos dedelete
(delete-expression) se não houver um deleter personalizado:
[util.smartptr.shared.dest]
~shared_ptr();
1 efeitos:
E se*this
éesvaziar ou compartilha a propriedade com outroshared_ptr
instância (use_count() > 1
), Não tem efeitos colaterais.Caso contrário, se*this
é proprietária um objetop
e um deléterd
, d(p)
é chamado.De outra forma,*this
é proprietária um ponteirop
edelete p
é chamado.Eu vou assumir ointenção é que uma implementação é necessária para excluir corretamente o ponteiro armazenado, mesmo que no escopo doshared_ptr
dtor, a expressão delete é mal formada ou invocaria o UB. (A expressão delete deve ser bem formada e ter um comportamento bem definido no ctor). Então, a questão é
Questão: Onde isso é necessário?
(Ou eu sou muito nit-picky e é óbvio de alguma forma que as implementações são necessárias para usar um "objeto proprietário"?)