Eu tenho um entendimento sólido da maioria das teorias de OO, mas a única coisa que me confunde muito são os destruidores virtuais.
Eu pensei que o destruidor sempre é chamado, não importa o que e para cada objeto na cadeia.
Quando você pretende torná-los virtuais e por quê?
virtual
certifica-se de que começa no topo e não no meio.Respostas:
Destruidores virtuais são úteis quando você pode excluir potencialmente uma instância de uma classe derivada através de um ponteiro para a classe base:
Aqui, você notará que eu não declarei ser o destruidor da Base
virtual
. Agora, vamos dar uma olhada no seguinte trecho:Como o destruidor do Base não é
virtual
eb
é umBase*
apontador para umDerived
objeto,delete b
tem um comportamento indefinido :Na maioria das implementações, a chamada ao destruidor será resolvida como qualquer código não virtual, o que significa que o destruidor da classe base será chamado, mas não o da classe derivada, resultando em um vazamento de recursos.
Para resumir, sempre faça destruidores das classes base
virtual
quando elas forem manipuladas polimorficamente.Se você deseja impedir a exclusão de uma instância por meio de um ponteiro de classe base, você pode tornar o destruidor da classe base protegido e não virtual; ao fazer isso, o compilador não permitirá que você chame
delete
um ponteiro de classe base.Você pode aprender mais sobre virtualidade e destruidor de classe base virtual neste artigo em Herb Sutter .
fonte
Base
eDerived
tiver todas as variáveis de armazenamento automático? ou seja, não há código personalizado "especial" ou adicional para executar no destruidor. Tudo bem deixar de escrever algum destruidor? Ou a classe derivada ainda terá um vazamento de memória?Um construtor virtual não é possível, mas o destruidor virtual é possível. Vamos experimentar .......
O código acima produz o seguinte:
A construção do objeto derivado segue a regra de construção, mas quando excluímos o ponteiro "b" (ponteiro de base), descobrimos que apenas o destruidor de base é chamado. Mas isso não deve acontecer. Para fazer a coisa apropriada, precisamos tornar o destruidor de base virtual. Agora vamos ver o que acontece a seguir:
A saída foi alterada da seguinte forma:
Portanto, a destruição do ponteiro base (que recebe uma alocação no objeto derivado!) Segue a regra de destruição, ou seja, primeiro o Derivado, depois a Base. Por outro lado, não há nada como um construtor virtual.
fonte
Declarar destruidores virtuais em classes base polimórficas. Este é o item 7 do C ++ efetivo de Scott Meyers . Meyers continua resumindo que, se uma classe tem alguma função virtual, ela deve ter um destruidor virtual e que as classes não projetadas para serem classes base ou não projetadas para serem usadas polimorficamente não devem declarar destruidores virtuais.
fonte
const Base& = make_Derived();
. Nesse caso, o destruidor doDerived
prvalue será chamado, mesmo que não seja virtual, para salvar a sobrecarga introduzida pelos vtables / vpointers. Claro que o escopo é bastante limitado. Andrei Alexandrescu mencionou isso em seu livro Modern C ++ Design .Lembre-se também de que excluir um ponteiro de classe base quando não houver destruidor virtual resultará em um comportamento indefinido . Algo que aprendi recentemente:
Como se deve substituir a exclusão no C ++?
Uso C ++ há anos e ainda consigo me enforcar.
fonte
Torne o destruidor virtual sempre que sua classe for polimórfica.
fonte
Chamando o destruidor através de um ponteiro para uma classe base
A chamada de destruidor virtual não é diferente de nenhuma outra chamada de função virtual.
Para
base->f()
, a chamada será enviada paraDerived::f()
, e é a mesma parabase->~Base()
- sua função primordial - oDerived::~Derived()
chamada.O mesmo acontece quando o destruidor está sendo chamado indiretamente, por exemplo
delete base;
. Adelete
declaração chamará obase->~Base()
que será enviado paraDerived::~Derived()
.Classe abstrata com destruidor não virtual
Se você não deseja excluir um objeto através de um ponteiro para sua classe base - não é necessário ter um destruidor virtual. Apenas faça
protected
para que não seja chamado acidentalmente:fonte
~Derived()
em todas as classes derivadas, mesmo que seja justo~Derived() = default
? Ou isso está implícito no idioma (tornando seguro omitir)?protected
seção ou para garantir que seja virtual usandooverride
.Eu gosto de pensar em interfaces e implementações de interfaces. Na linguagem C ++, a interface é pura classe virtual. O destruidor faz parte da interface e espera-se que seja implementado. Portanto, o destruidor deve ser totalmente virtual. E o construtor? O construtor não faz parte da interface porque o objeto é sempre instanciado explicitamente.
fonte
virtual
em uma classe base, ele estará automaticamentevirtual
em uma classe derivada, mesmo que não seja declarado.A palavra-chave virtual para destruidor é necessária quando você deseja que diferentes destruidores sigam a ordem correta enquanto os objetos estão sendo excluídos pelo ponteiro da classe base. por exemplo:
Se o destruidor da classe base for virtual, os objetos serão destruídos em uma ordem (primeiro o objeto derivado e a base). Se o destruidor da classe base NÃO for virtual, apenas o objeto da classe base será excluído (porque o ponteiro é da classe base "Base * myObj"). Portanto, haverá vazamento de memória para o objeto derivado.
fonte
Para ser simples, o destruidor virtual é destruir os recursos em uma ordem adequada, quando você exclui um ponteiro de classe base apontando para o objeto de classe derivado.
fonte
delete
um ponteiro base leva a um comportamento indefinido.Os destruidores da classe base virtual são "práticas recomendadas" - você deve sempre usá-los para evitar (difíceis de detectar) vazamentos de memória. Usando-os, você pode ter certeza de que todos os destruidores da cadeia de herança de suas classes estão sendo chamados (na ordem correta). Herdar de uma classe base usando o destruidor virtual também torna o destruidor da classe herdada automaticamente virtual (para que você não precise digitar 'virtual' na declaração do destruidor da classe herdada).
fonte
Se você usar
shared_ptr
(apenas shared_ptr, não unique_ptr), não precisará ter o destruidor da classe base virtual:resultado:
fonte
virtual
palavra-chave pode salvá-lo de muita agonia.O que é um destruidor virtual ou como usar o destruidor virtual
Um destruidor de classe é uma função com o mesmo nome da classe anterior a ~ que realocará a memória alocada pela classe. Por que precisamos de um destruidor virtual
Veja o exemplo a seguir com algumas funções virtuais
A amostra também informa como você pode converter uma letra para superior ou inferior
No exemplo acima, você pode ver que o destruidor das classes MakeUpper e MakeLower não é chamado.
Veja a próxima amostra com o destruidor virtual
O destruidor virtual chamará explicitamente o destruidor de tempo de execução mais derivado da classe, para poder limpar o objeto de maneira adequada.
Ou visite o link
https://web.archive.org/web/20130822173509/http://www.programminggallery.com/article_details.php?article_id=138
fonte
quando você precisar chamar o destruidor de classe derivada da classe base. você precisa declarar destruidor da classe base virtual na classe base.
fonte
Penso que o cerne desta questão é sobre métodos virtuais e polimorfismo, não o destruidor especificamente. Aqui está um exemplo mais claro:
Irá imprimir:
Sem
virtual
ele será impresso:E agora você deve entender quando usar destruidores virtuais.
fonte
B b{}; A& a{b}; a.foo();
. A verificaçãoNULL
- que deve sernullptr
- antes dedelete
- com indentação incorreta - não é necessária:delete nullptr;
é definida como não operacional. Se houver alguma coisa, você deve ter verificado isso antes de ligar->foo()
, caso contrário comportamento indefinido pode ocorrer se anew
alguma forma falhou).delete
umNULL
ponteiro (ou seja, você não precisa daif (a != NULL)
guarda).Eu pensei que seria benéfico discutir o comportamento "indefinido", ou pelo menos o comportamento indefinido "travar" que pode ocorrer ao excluir através de uma classe base (/ struct) sem um destruidor virtual ou, mais precisamente, nenhuma tabela. O código abaixo lista algumas estruturas simples (o mesmo se aplica às classes).
Não estou sugerindo se você precisa de destruidores virtuais ou não, embora eu pense que, em geral, é uma boa prática tê-los. Estou apenas apontando o motivo pelo qual você pode acabar com uma falha se sua classe base (/ struct) não possui uma vtable e sua classe derivada (/ struct) e você exclui um objeto por meio de uma classe base (/ struct) ponteiro. Nesse caso, o endereço que você passa para a rotina livre do heap é inválido e, portanto, o motivo da falha.
Se você executar o código acima, verá claramente quando o problema ocorrer. Quando o ponteiro this da classe base (/ struct) for diferente do ponteiro this da classe derivada (/ struct), você encontrará esse problema. No exemplo acima, struct aeb não possuem vtables. estruturas c e d têm vtables. Assim, um ponteiro a ou b para uma instância de objeto ac ou d será corrigido para contabilizar a tabela. Se você passar esse ponteiro a ou b para excluí-lo, ele falhará devido ao endereço ser inválido na rotina livre da pilha.
Se você planeja excluir instâncias derivadas que possuem vtables dos ponteiros da classe base, é necessário garantir que a classe base tenha uma vtable. Uma maneira de fazer isso é adicionar um destruidor virtual, que você pode desejar limpar os recursos adequadamente.
fonte
Uma definição básica sobre
virtual
é determinar se uma função de membro de uma classe pode ser substituída em suas classes derivadas.O D-tor de uma classe é chamado basicamente no final do escopo, mas há um problema, por exemplo, quando definimos uma instância no Heap (alocação dinâmica), devemos excluí-lo manualmente.
Assim que a instrução é executada, o destruidor da classe base é chamado, mas não para o derivado.
Um exemplo prático é quando, no campo de controle, você precisa manipular efetores, atuadores.
No final do escopo, se o destruidor de um dos elementos de energia (Atuador) não for chamado, haverá consequências fatais.
fonte
Qualquer classe herdada publicamente, polimórfica ou não, deve ter um destruidor virtual. Em outras palavras, se puder ser apontado por um ponteiro de classe base, sua classe base deverá ter um destruidor virtual.
Se virtual, o destruidor de classe derivado é chamado, então o construtor da classe base. Se não for virtual, apenas o destruidor da classe base será chamado.
fonte