O Sr. Lidström e eu tivemos uma discussão :)
A alegação do Sr. Lidström é que uma construção shared_ptr<Base> p(new Derived);
não exige que a Base tenha um destruidor virtual:
Armen Tsirunyan : "Sério? O shared_ptr será limpo corretamente? Você poderia, 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 Concreta. Isso é conhecido como RAII na comunidade C ++. Meu conselho é que você aprenda tudo que puder sobre RAII. Isso tornará sua codificação C ++ muito mais fácil quando você usar RAII em todas as situações. "
Armen Tsirunyan : "Eu sei sobre RAII e também sei que, eventualmente, o destruidor shared_ptr pode excluir o px armazenado quando pn atingir 0. Mas se px tiver um ponteiro de tipo estático para
Base
e um ponteiro de tipo dinâmico paraDerived
, a menos queBase
tenha um destruidor virtual, isso resultará em comportamento indefinido. Corrija-me se eu estiver errado. "Daniel Lidström : "O shared_ptr sabe que o tipo estático é concreto. Ele sabe disso desde que o passei em seu construtor! Parece um pouco mágico, mas posso garantir que é intencional e extremamente bom."
Então, nos julgue. Como é possível (se for) implementar shared_ptr sem exigir que as classes polimórficas tenham um destruidor virtual? desde já, obrigado
fonte
shared_ptr<void> p(new Derived)
também irá destruir oDerived
objeto pelo seu destruidor, independente se forvirtual
ou não.shared_ptr<T>( (T*)new U() )
ondestruct U:T
não vai fazer a coisa certa (e isso pode ser feito indiretamente facilmente, como uma função que recebe aT*
e é passada aU*
)Respostas:
Sim, é possível implementar shared_ptr dessa forma. Boost o faz e o padrão C ++ 11 também requer esse comportamento. Como uma flexibilidade adicional, shared_ptr gerencia mais do que apenas um contador de referência. Um assim chamado deleter é geralmente colocado no mesmo bloco de memória que também contém os contadores de referência. Mas a parte divertida é que o tipo desse deletador não faz parte do tipo shared_ptr. Isso é chamado de "apagamento de tipo" e é basicamente a mesma técnica usada para implementar as "funções polimórficas" boost :: function ou std :: function para ocultar o tipo do functor real. Para fazer seu exemplo funcionar, precisamos de um construtor de modelo:
template<class T> class shared_ptr { public: ... template<class Y> explicit shared_ptr(Y* p); ... };
Então, se você usar isso com suas classes Base e Derivada ...
class Base {}; class Derived : public Base {}; int main() { shared_ptr<Base> sp (new Derived); }
... o construtor de modelo com Y = Derived é usado para construir o objeto shared_ptr. O construtor tem, portanto, a chance de criar o objeto 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 deletador criado anteriormente e com reconhecimento de derivado será usado para descartar o objeto.
O padrão C ++ 11 tem o seguinte a dizer sobre este construtor (20.7.2.2.1):
E para o destruidor (20.7.2.2.2):
(ênfase em negrito é minha).
fonte
the upcoming standard also requires this behaviour
: (a) Qual padrão e (b) você pode fornecer uma referência (ao padrão)?add a comment
. IMO, é mais doBoost does this
quethe Standard requires
. Não acho que o Padrão exija isso do que estou entendendo. Falando no exemplo do @sellibitzeshared_ptr<Base> sp (new Derived);
, Requer deconstructor
apenas pedir paradelete Derived
ser bem definido e bem formado. Para a especificação dedestructor
, há também ump
, mas não acho que se refira ap
na especificação deconstructor
.Quando shared_ptr é criado, ele armazena um objeto deleter dentro de si. Este objeto é chamado quando o shared_ptr está prestes a liberar o recurso apontado. Como você sabe como destruir o recurso no ponto de construção, pode usar shared_ptr com tipos incompletos. Quem criou o shared_ptr armazenou um deleter correto lá.
Por exemplo, você pode criar um apagador personalizado:
void DeleteDerived(Derived* d) { delete d; } // EDIT: no conversion needed. shared_ptr<Base> p(new Derived, DeleteDerived);
p irá chamar DeleteDerived para destruir o objeto apontado. A implementação faz isso automaticamente.
fonte
shared_ptr
como um atributo.Simplesmente,
shared_ptr
usa a função deleter especial que é criada pelo construtor que sempre usa o destruidor do objeto dado e não o destruidor do Base, isso é um pouco trabalhoso com a metaprogramação do template, mas funciona.Algo parecido
template<typename SomeType> shared_ptr(SomeType *p) { this->destroyer = destroyer_function<SomeType>(p); ... }
fonte