magia shared_ptr :)

O Sr. Lidström e eu tivemos uma discussão 🙂

A alegação do Sr. Lidström é que uma construção shared_ptr p(new Derived); não requer que o Base tenha um destruidor virtual:

Armen Tsirunyan : “Realmente? O shared_ptr limpará corretamente? Você poderia, por favor, neste caso, demonstrar como esse efeito poderia ser implementado?”

Daniel Lidström : “O shared_ptr usa seu próprio destruidor para excluir a instância do Concrete. Isso é conhecido como RAII dentro da comunidade C ++. Meu conselho é que você aprenda tudo o que puder sobre o RAII. Tornará sua codificação C ++ muito mais fácil quando você usar RAII em todas as situações “.

Armen Tsirunyan : “Eu sei sobre o RAII, e também sei que eventualmente o destruidor shared_ptr pode deletar o px armazenado quando o pn atingir 0. Mas se o px tiver um ponteiro de tipo estático para Base e um ponteiro de tipo dynamic para Derived , a menos que Base tenha um virtual destruidor, isso resultará em um comportamento indefinido. Corrija-me se estiver errado. ”

Daniel Lidström : “O shared_ptr sabe que o tipo estático é o Concrete. Ele sabe disso desde que eu o passei em seu construtor! Parece um pouco como mágica, mas posso garantir que é por design e extremamente legal.”

Então, nos julgue. Como é possível (se for) implementar o shared_ptr sem exigir que classs polimórficas tenham o destruidor virtual? desde já, obrigado

Sim, é possível implementar shared_ptr dessa maneira. O Boost faz e o padrão C ++ 11 também requer esse comportamento. Como uma flexibilidade adicional, o shared_ptr gerencia mais do que apenas um contador de referência. Um chamado deleter geralmente é colocado no mesmo bloco de memory que também contém os contadores de referência. Mas a parte divertida é que o tipo desse deleter não faz parte do tipo shared_ptr. Isso é chamado de “tipo de apagamento” e é basicamente a mesma técnica usada para implementar as funções “polymorphic” boost :: function ou std :: function para ocultar o tipo do functor real. Para que seu exemplo funcione, precisamos de um construtor de modelo:

 template class shared_ptr { public: ... template explicit shared_ptr(Y* p); ... }; 

Então, se você usar isso com suas classs Base e Derived …

 class Base {}; class Derived : public Base {}; int main() { shared_ptr sp (new Derived); } 

… o construtor de modelo com Y = Derived é usado para construir o object shared_ptr. O construtor tem, portanto, a chance de criar o object deleter apropriado e contadores de referência e armazena um ponteiro para este bloco de controle como um membro de dados. Se o contador de referência chegar a zero, o deleter criado anteriormente e o Derived-aware serão usados ​​para descartar o object.

O padrão C ++ 11 tem o seguinte a dizer sobre este construtor (20.7.2.2.1):

Requer: p deve ser conversível em T* . Y será um tipo completo. A expressão delete p deve ser bem formada, deve ter um comportamento bem definido e não deve lançar exceções.

Efeitos: Constrói um object shared_ptr que possui o ponteiro p .

E para o destruidor (20.7.2.2.2):

Efeitos: Se *this estiver vazio ou compartilhar a propriedade com outra instância shared_ptr ( use_count() > 1 ), não haverá efeitos colaterais. Caso contrário, se *this possui um object p e um deletador d , d(p) é chamado. Caso contrário, se *this possui um ponteiro p , e delete p é chamado.

(a ênfase na fonte em negrito é minha).

Quando shared_ptr é criado, ele armazena um object deleter dentro de si. Este object é chamado quando o shared_ptr está prestes a liberar o recurso apontado. Como você sabe como destruir o recurso no ponto de construção, você pode usar shared_ptr com tipos incompletos. Quem criou o shared_ptr armazenou um deléter correto lá.

Por exemplo, você pode criar um deleter personalizado:

 void DeleteDerived(Derived* d) { delete d; } // EDIT: no conversion needed. shared_ptr p(new Derived, DeleteDerived); 

p chamará DeleteDerived para destruir o object pontudo. A implementação faz isso automaticamente.

Simplesmente,

shared_ptr usa a function deleter especial que é criada pelo construtor que sempre usa o destruidor do object fornecido e não o destruidor do Base, isso é um pouco de trabalho com metaprogramação de modelo, mas funciona.

Algo parecido

 template shared_ptr(SomeType *p) { this->destroyer = destroyer_function(p); ... }