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_allocou 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 ponteiropedelete 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"?)

questionAnswers(1)

yourAnswerToTheQuestion