Quando usar destruidores virtuais?

1486

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ê?

Lodle
fonte
6
Veja isto: Destruidor virtual
Naveen
146
Todo destruidor é derrubado, não importa o quê. virtualcertifica-se de que começa no topo e não no meio.
Mooing Duck
15
pergunta relacionada: Quando você não deve usar destruidores virtuais?
Eitan T
@MooingDuck isso é um comentário enganador.
Euri Pinhollow
1
@FranklinYu, é bom que você tenha perguntado, porque agora não consigo ver nenhum problema com esse comentário (exceto tentar responder nos comentários).
Euri Pinhollow

Respostas:

1572

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:

class Base 
{
    // some virtual methods
};

class Derived : public Base
{
    ~Derived()
    {
        // Do some important cleanup
    }
};

Aqui, você notará que eu não declarei ser o destruidor da Base virtual. Agora, vamos dar uma olhada no seguinte trecho:

Base *b = new Derived();
// use b
delete b; // Here's the problem!

Como o destruidor do Base não é virtuale bé um Base*apontador para um Derivedobjeto, delete btem um comportamento indefinido :

[In delete b], se o tipo estático do objeto a ser excluído for diferente do seu tipo dinâmico, o tipo estático deve ser uma classe base do tipo dinâmico do objeto a ser excluído e o tipo estático deve ter um destruidor virtual ou o 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 virtualquando 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 deleteum ponteiro de classe base.

Você pode aprender mais sobre virtualidade e destruidor de classe base virtual neste artigo em Herb Sutter .

Luc Touraille
fonte
174
Isso explicaria por que tive vazamentos maciços usando uma fábrica que fiz antes. Tudo faz sentido agora. Obrigado
Lodle
8
Bem, este é um mau exemplo, pois não há membros de dados. E se Basee Derivedtiver 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?
bobobobo
28
No artigo de Herb Sutter: "Diretriz 4: Um destruidor de classe base deve ser público e virtual, ou protegido e não virtual".
Sundae
3
Também do artigo - 'se você excluir polimorficamente sem um destruidor virtual, convoca o temido espectro de "comportamento indefinido", um espectro que eu pessoalmente preferiria não encontrar em um beco moderadamente bem iluminado, muito obrigado. " lol
Bondolin
219

Um construtor virtual não é possível, mas o destruidor virtual é possível. Vamos experimentar .......

#include <iostream>

using namespace std;

class Base
{
public:
    Base(){
        cout << "Base Constructor Called\n";
    }
    ~Base(){
        cout << "Base Destructor called\n";
    }
};

class Derived1: public Base
{
public:
    Derived1(){
        cout << "Derived constructor called\n";
    }
    ~Derived1(){
        cout << "Derived destructor called\n";
    }
};

int main()
{
    Base *b = new Derived1();
    delete b;
}

O código acima produz o seguinte:

Base Constructor Called
Derived constructor called
Base Destructor called

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:

#include <iostream>

using namespace std;

class Base
{ 
public:
    Base(){
        cout << "Base Constructor Called\n";
    }
    virtual ~Base(){
        cout << "Base Destructor called\n";
    }
};

class Derived1: public Base
{
public:
    Derived1(){
        cout << "Derived constructor called\n";
    }
    ~Derived1(){
        cout << "Derived destructor called\n";
    }
};

int main()
{
    Base *b = new Derived1();
    delete b;
}

A saída foi alterada da seguinte forma:

Base Constructor Called
Derived Constructor called
Derived destructor called
Base destructor called

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.

Tunvir Rahman Tusher
fonte
1
"Construtor virtual não é possível" significa que você não precisa escrever o construtor virtual sozinho. A construção do objeto derivado deve seguir a cadeia de construção derivada da base. Portanto, você não precisa escrever a palavra-chave virtual para o seu construtor. Obrigado
Tunvir Rahman Tusher
4
@Murkantilism, "construtores virtuais não podem ser feitos" é verdade. Um construtor não pode ser marcado como virtual.
Creub #
1
@cmeub, Mas existe um idioma para alcançar o que você deseja de um construtor virtual. Veja parashift.com/c++-faq-lite/virtual-ctors.html
cape1232
@TunvirRahmanTusher você poderia explicar por que o Destrutor de Base é chamado ??
Rimalonfire 11/11
@rimiro É automático por c ++. você pode seguir o link stackoverflow.com/questions/677620/…
Tunvir Rahman Tusher
195

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.

Bill the Lizard
fonte
14
+ "Se uma classe tem alguma função virtual, ela deve ter um destruidor virtual e as classes que não foram projetadas para serem classes base ou não foram projetadas para serem usadas polimorficamente não devem declarar destruidores virtuais.": Existem casos em que faz sentido quebrar essa regra? Caso contrário, faria sentido que o compilador verifique essa condição e emita um erro, caso não esteja satisfeito?
Giorgio
@ Giorgio Não conheço exceções à regra. Mas eu não me classificaria como especialista em C ++, portanto, você pode postar isso como uma pergunta separada. Um aviso do compilador (ou um aviso de uma ferramenta de análise estática) faz sentido para mim.
Bill the Lizard
10
As classes podem ser projetadas para não serem excluídas por meio de um ponteiro de um determinado tipo, mas ainda possuem funções virtuais - o exemplo típico é uma interface de retorno de chamada. Não se exclui sua implementação por meio de um ponteiro de interface de retorno de chamada, pois é apenas para assinatura, mas possui funções virtuais.
Dascandy
3
@dascandy Exatamente - que ou todas as muitas outras situações em que usamos o comportamento polimórfico mas não realizam o gerenciamento de armazenamento via ponteiros - por exemplo, mantendo objetos automáticos ou estática de duração, com ponteiros única usados como rotas de observação. Não há necessidade / objetivo na implementação de um destruidor virtual nesses casos. Como estamos citando pessoas aqui, prefiro Sutter de cima: "Diretriz 4: Um destruidor de classe base deve ser público e virtual, ou protegido e não virtual". O último garante que qualquer um que acidentalmente tente excluir por meio de um ponteiro base seja mostrado o erro de seus caminhos
underscore_d
1
@ Giorgio Na verdade, há um truque que se pode usar e evitar uma chamada virtual para um destruidor: vincular através de uma referência constante um objeto derivado a uma base, como const Base& = make_Derived();. Nesse caso, o destruidor do Derivedprvalue 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 .
Vsoftco 2/11
46

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.

BigSandwich
fonte
Eu dei uma olhada nessa sua pergunta e vi que você havia declarado o destruidor de base como virtual. Então "excluir um ponteiro de classe base quando não houver destruidor virtual resultará em comportamento indefinido" permanece válido com relação a essa pergunta sua? Como nessa pergunta, quando você chamou delete, a classe derivada (criada por seu novo operador) é verificada primeiro por uma versão compatível. Desde que encontrou um lá, foi chamado. Então, você não acha que seria melhor dizer que "excluir um ponteiro de classe base quando não houver destruidor resultará em comportamento indefinido"?
ubuntugod
Isso é praticamente a mesma coisa. O construtor padrão não é virtual.
BigSandwich 26/02
41

Torne o destruidor virtual sempre que sua classe for polimórfica.

yesraaj
fonte
13

Chamando o destruidor através de um ponteiro para uma classe base

struct Base {
  virtual void f() {}
  virtual ~Base() {}
};

struct Derived : Base {
  void f() override {}
  ~Derived() override {}
};

Base* base = new Derived;
base->f(); // calls Derived::f
base->~Base(); // calls Derived::~Derived

A chamada de destruidor virtual não é diferente de nenhuma outra chamada de função virtual.

Para base->f(), a chamada será enviada para Derived::f(), e é a mesma para base->~Base()- sua função primordial - oDerived::~Derived() chamada.

O mesmo acontece quando o destruidor está sendo chamado indiretamente, por exemplo delete base;. A deletedeclaração chamará o base->~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 protectedpara que não seja chamado acidentalmente:

// library.hpp

struct Base {
  virtual void f() = 0;

protected:
  ~Base() = default;
};

void CallsF(Base& base);
// CallsF is not going to own "base" (i.e. call "delete &base;").
// It will only call Base::f() so it doesn't need to access Base::~Base.

//-------------------
// application.cpp

struct Derived : Base {
  void f() override { ... }
};

int main() {
  Derived derived;
  CallsF(derived);
  // No need for virtual destructor here as well.
}
Abyx
fonte
É necessário declarar explicitamente ~Derived()em todas as classes derivadas, mesmo que seja justo ~Derived() = default? Ou isso está implícito no idioma (tornando seguro omitir)?
Ponkadoodle
@ Wallacoloo não, apenas declare quando for necessário. Por exemplo, para colocar na protectedseção ou para garantir que seja virtual usando override.
Abyx
9

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.

Dragan Ostojic
fonte
2
É uma perspectiva diferente sobre a mesma pergunta. Se pensarmos em termos de interfaces em vez de classe base versus classe derivada, é uma conclusão natural: se fizer parte da interface, torne-a virtual. Se não for, não.
Dragan Ostojic 9/11/12
2
+1 por declarar a semelhança do conceito de interface OO e uma classe virtual pura em C ++ . Em relação ao destruidor, espera-se que seja implementado : isso geralmente é desnecessário. A menos que uma classe esteja gerenciando um recurso, como memória bruta alocada dinamicamente (por exemplo, não por meio de um ponteiro inteligente), um identificador de arquivo ou um identificador de banco de dados, usando o destruidor padrão criado pelo compilador, é bom nas classes derivadas. E observe que, se um destruidor (ou qualquer função) for declarado virtualem uma classe base, ele estará automaticamente virtualem uma classe derivada, mesmo que não seja declarado.
21813 DavidRR
Isso perde os detalhes cruciais de que o destruidor não é necessariamente parte da interface. Pode-se programar facilmente classes que possuem funções polimórficas, mas que o chamador não gerencia / não tem permissão para excluir. Então um destruidor virtual não tem propósito. Obviamente, para garantir isso, o destruidor não virtual - provavelmente padrão - deve ser não público. Se eu tivesse que adivinhar, diria que essas classes são mais usadas internamente em projetos, mas isso não as torna menos relevantes como exemplo / nuance em tudo isso.
Underscore_d
8

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:

Base *myObj = new Derived();
// Some code which is using myObj object
myObj->fun();
//Now delete the object
delete myObj ; 

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.

Mukul Kashmira
fonte
7

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.

 #include<iostream>
 using namespace std;
 class B{
    public:
       B(){
          cout<<"B()\n";
       }
       virtual ~B(){ 
          cout<<"~B()\n";
       }
 };
 class D: public B{
    public:
       D(){
          cout<<"D()\n";
       }
       ~D(){
          cout<<"~D()\n";
       }
 };
 int main(){
    B *b = new D();
    delete b;
    return 0;
 }

OUTPUT:
B()
D()
~D()
~B()

==============
If you don't give ~B()  as virtual. then output would be 
B()
D()
~B()
where destruction of ~D() is not done which leads to leak

Prakash GiBBs
fonte
Não ter o destruidor virtual básico e chamar deleteum ponteiro base leva a um comportamento indefinido.
James Adkison
@JamesAdkison por que isso leva a um comportamento indefinido ??
Rimalonfire 11/11
@rimiro É o que diz o padrão . Não tenho uma cópia, mas o link leva você a um comentário em que alguém faz referência à localização dentro do padrão.
precisa
@rimiro "Se a exclusão, portanto, pode ser realizada polimorficamente através da interface da classe base, ela deve se comportar virtualmente e deve ser virtual. De fato, a linguagem exige isso - se você excluir polimorficamente sem um destruidor virtual, convoque o temido espectro de "comportamento indefinido", um fantasma que eu pessoalmente preferiria não encontrar em um beco moderadamente bem iluminado, muito obrigado. " ( Gotw.ca/publications/mill18.htm ) - Herb Sutter
James Adkison
4

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).

Trantor
fonte
4

Se você usar shared_ptr(apenas shared_ptr, não unique_ptr), não precisará ter o destruidor da classe base virtual:

#include <iostream>
#include <memory>

using namespace std;

class Base
{
public:
    Base(){
        cout << "Base Constructor Called\n";
    }
    ~Base(){ // not virtual
        cout << "Base Destructor called\n";
    }
};

class Derived: public Base
{
public:
    Derived(){
        cout << "Derived constructor called\n";
    }
    ~Derived(){
        cout << "Derived destructor called\n";
    }
};

int main()
{
    shared_ptr<Base> b(new Derived());
}

resultado:

Base Constructor Called
Derived constructor called
Derived destructor called
Base Destructor called
Zhenxiao Hao
fonte
Embora isso seja possível, eu desencorajaria qualquer um a usar isso. A sobrecarga de um destruidor virtual é minúscula e isso apenas torna possível a bagunça, especialmente por um programador menos experiente, que não sabe disso. Essa pequena virtualpalavra-chave pode salvá-lo de muita agonia.
Michal Štein 21/03
3

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

#include "stdafx.h"
#include<iostream>
using namespace std;
// program to convert the lower to upper orlower
class convertch
{
public:
  //void convertch(){};
  virtual char* convertChar() = 0;
  ~convertch(){};
};

class MakeLower :public convertch
{
public:
  MakeLower(char *passLetter)
  {
    tolower = true;
    Letter = new char[30];
    strcpy(Letter, passLetter);
  }

  virtual ~MakeLower()
  {
    cout<< "called ~MakeLower()"<<"\n";
    delete[] Letter;
  }

  char* convertChar()
  {
    size_t len = strlen(Letter);
    for(int i= 0;i<len;i++)
      Letter[i] = Letter[i] + 32;
    return Letter;
  }

private:
  char *Letter;
  bool tolower;
};

class MakeUpper : public convertch
{
public:
  MakeUpper(char *passLetter)
  {
    Letter = new char[30];
    toupper = true;
    strcpy(Letter, passLetter);
  }

  char* convertChar()
  {   
    size_t len = strlen(Letter);
    for(int i= 0;i<len;i++)
      Letter[i] = Letter[i] - 32;
    return Letter;
  }

  virtual ~MakeUpper()
  {
    cout<< "called ~MakeUpper()"<<"\n";
    delete Letter;
  }

private:
  char *Letter;
  bool toupper;
};


int _tmain(int argc, _TCHAR* argv[])
{
  convertch *makeupper = new MakeUpper("hai"); 
  cout<< "Eneterd : hai = " <<makeupper->convertChar()<<" ";     
  delete makeupper;
  convertch *makelower = new MakeLower("HAI");;
  cout<<"Eneterd : HAI = " <<makelower->convertChar()<<" "; 
  delete makelower;
  return 0;
}

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

#include "stdafx.h"
#include<iostream>

using namespace std;
// program to convert the lower to upper orlower
class convertch
{
public:
//void convertch(){};
virtual char* convertChar() = 0;
virtual ~convertch(){}; // defined the virtual destructor

};
class MakeLower :public convertch
{
public:
MakeLower(char *passLetter)
{
tolower = true;
Letter = new char[30];
strcpy(Letter, passLetter);
}
virtual ~MakeLower()
{
cout<< "called ~MakeLower()"<<"\n";
      delete[] Letter;
}
char* convertChar()
{
size_t len = strlen(Letter);
for(int i= 0;i<len;i++)
{
Letter[i] = Letter[i] + 32;

}

return Letter;
}

private:
char *Letter;
bool tolower;

};
class MakeUpper : public convertch
{
public:
MakeUpper(char *passLetter)
{
Letter = new char[30];
toupper = true;
strcpy(Letter, passLetter);
}
char* convertChar()
{

size_t len = strlen(Letter);
for(int i= 0;i<len;i++)
{
Letter[i] = Letter[i] - 32;
}
return Letter;
}
virtual ~MakeUpper()
{
      cout<< "called ~MakeUpper()"<<"\n";
delete Letter;
}
private:
char *Letter;
bool toupper;
};


int _tmain(int argc, _TCHAR* argv[])
{

convertch *makeupper = new MakeUpper("hai");

cout<< "Eneterd : hai = " <<makeupper->convertChar()<<" \n";

delete makeupper;
convertch *makelower = new MakeLower("HAI");;
cout<<"Eneterd : HAI = " <<makelower->convertChar()<<"\n ";


delete makelower;
return 0;
}

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

user2578542
fonte
2

quando você precisar chamar o destruidor de classe derivada da classe base. você precisa declarar destruidor da classe base virtual na classe base.

user2641018
fonte
2

Penso que o cerne desta questão é sobre métodos virtuais e polimorfismo, não o destruidor especificamente. Aqui está um exemplo mais claro:

class A
{
public:
    A() {}
    virtual void foo()
    {
        cout << "This is A." << endl;
    }
};

class B : public A
{
public:
    B() {}
    void foo()
    {
        cout << "This is B." << endl;
    }
};

int main(int argc, char* argv[])
{
    A *a = new B();
    a->foo();
    if(a != NULL)
    delete a;
    return 0;
}

Irá imprimir:

This is B.

Sem virtualele será impresso:

This is A.

E agora você deve entender quando usar destruidores virtuais.

gonjay
fonte
Não, isso apenas recapitula os conceitos básicos absolutos das funções virtuais, ignorando totalmente as nuances de quando / por que o destruidor deveria ser um - o que não é tão intuitivo, por isso o OP fez a pergunta. (Além disso, por que a alocação dinâmica desnecessária aqui? Basta fazer B b{}; A& a{b}; a.foo();. A verificação NULL- que deve ser nullptr- antes de delete- 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 a newalguma forma falhou).
underscore_d
2
É seguro chamar deleteum NULLponteiro (ou seja, você não precisa da if (a != NULL)guarda).
James Adkison
@SaileshD Sim, eu sei. Isso é o que eu disse no meu comentário
James Adkison
1

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).

#include <iostream>
using namespace std;

struct a
{
    ~a() {}

    unsigned long long i;
};

struct b : a
{
    ~b() {}

    unsigned long long j;
};

struct c : b
{
    ~c() {}

    virtual void m3() {}

    unsigned long long k;
};

struct d : c
{
    ~d() {}

    virtual void m4() {}

    unsigned long long l;
};

int main()
{
    cout << "sizeof(a): " << sizeof(a) << endl;
    cout << "sizeof(b): " << sizeof(b) << endl;
    cout << "sizeof(c): " << sizeof(c) << endl;
    cout << "sizeof(d): " << sizeof(d) << endl;

    // No issue.

    a* a1 = new a();
    cout << "a1: " << a1 << endl;
    delete a1;

    // No issue.

    b* b1 = new b();
    cout << "b1: " << b1 << endl;
    cout << "(a*) b1: " << (a*) b1 << endl;
    delete b1;

    // No issue.

    c* c1 = new c();
    cout << "c1: " << c1 << endl;
    cout << "(b*) c1: " << (b*) c1 << endl;
    cout << "(a*) c1: " << (a*) c1 << endl;
    delete c1;

    // No issue.

    d* d1 = new d();
    cout << "d1: " << d1 << endl;
    cout << "(c*) d1: " << (c*) d1 << endl;
    cout << "(b*) d1: " << (b*) d1 << endl;
    cout << "(a*) d1: " << (a*) d1 << endl;
    delete d1;

    // Doesn't crash, but may not produce the results you want.

    c1 = (c*) new d();
    delete c1;

    // Crashes due to passing an invalid address to the method which
    // frees the memory.

    d1 = new d();
    b1 = (b*) d1;
    cout << "d1: " << d1 << endl;
    cout << "b1: " << b1 << endl;
    delete b1;  

/*

    // This is similar to what's happening above in the "crash" case.

    char* buf = new char[32];
    cout << "buf: " << (void*) buf << endl;
    buf += 8;
    cout << "buf after adding 8: " << (void*) buf << endl;
    delete buf;
*/
}

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.

nickdu
fonte
0

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.

#include <iostream>

class Mother{

public:

    Mother(){

          std::cout<<"Mother Ctor"<<std::endl;
    }

    virtual~Mother(){

        std::cout<<"Mother D-tor"<<std::endl;
    }


};

class Child: public Mother{

    public:

    Child(){

        std::cout<<"Child C-tor"<<std::endl;
    }

    ~Child(){

         std::cout<<"Child D-tor"<<std::endl;
    }
};

int main()
{

    Mother *c = new Child();
    delete c;

    return 0;
}
rekkalmd
fonte
-1

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.

Syed H
fonte
Eu diria que isso só é necessário "se puder ser apontado por um ponteiro de classe base" e puder ser excluído publicamente. Mas acho que não dói adquirir o hábito de adicionar dtors virtuais, caso sejam necessários mais tarde.
underscore_d