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"&nbsp;"objeto proprietário" (*) é criado durante a construção doshared_ptr&nbsp;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&nbsp;destruidor deinterface.

Questão:&nbsp;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&nbsp;deve ser conversível paraT*. Y&nbsp;deve ser um tipo completo. A expressãodelete p&nbsp;deve ser bem formado, deve ter um comportamento bem definido, e não deve lançar exceções.

4 efeitos: constrói umshared_ptr&nbsp;objeto que possui o ponteirop.

5 pós-condições:use_count() == 1 && get() == p.

6 lances:bad_allocou uma exceção definida pela implementação quando um recurso diferente da memória não pôde ser obtido.

Nota:&nbsp;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&nbsp;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&nbsp;é umdelémetro personalizado, o destruidor deshared_ptr&nbsp;é definido em termos dedelete&nbsp;(delete-expression) se não houver um deleter personalizado:

[util.smartptr.shared.dest]

~shared_ptr();

1 efeitos:

E se*this&nbsp;éesvaziar&nbsp;ou compartilha a propriedade com outroshared_ptr&nbsp;instância (use_count() > 1), Não tem efeitos colaterais.Caso contrário, se*this é proprietária&nbsp;um objetop&nbsp;e um deléterd, d(p)&nbsp;é chamado.De outra forma,*this é proprietária&nbsp;um ponteiropedelete p&nbsp;é chamado.

Eu vou assumir ointenção&nbsp;é que uma implementação é necessária para excluir corretamente o ponteiro armazenado, mesmo que no escopo doshared_ptr&nbsp;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:&nbsp;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"?)