Pergunta

Eu tenho uma sólida compreensão da maioria teoria OO, mas a única coisa que me confunde um monte é destruidores virtuais.

Eu pensei que o destruidor sempre fica sem chamado importa o que e para cada objeto na cadeia.

Quando você está destinado a torná-los virtual e por quê?

Foi útil?

Solução

destruidores virtuais são úteis quando você pode potencialmente eliminar uma instância de uma classe derivada através de um ponteiro para classe base:

class Base 
{
    // some virtual methods
};

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

Aqui, você vai notar que eu não declarar destruidor de base a ser virtual. Agora, vamos dar uma olhada no seguinte trecho:

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

Desde destruidor de base não virtual é e b é um apontador Base* a um objeto Derived, delete b tem indefinido comportamento :

[Em delete b], se o tipo estático do objeto a ser excluído é diferente de seu tipo dinâmica, a estática tipo deve ser uma classe base do tipo dinâmico do objeto a ser excluído eo tipo estático deve ter um destrutor virtual ou o comportamento é indefinido .

Na maioria das implementações, a chamada para o destruidor será resolvido como qualquer código não-virtual, o que significa que o destrutor da classe base será chamado, mas não a da classe derivada, resultando em um dos recursos vazar.

Para resumir, fazer sempre destruidores das classes de base virtual quando eles foram feitos para ser manipulado polymorphically.

Se você quer evitar a eliminação de uma instância através de um ponteiro de classe base, você pode fazer o destruidor de classe base protegida e não virtual; ao fazê-lo, o compilador não vai deixar você chamar delete em um ponteiro de classe base.

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

Outras dicas

Um construtor virtual não é possível, mas destruidor virtual é possível. Deixe-nos 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;
}

A saída de código acima o seguinte:

Base Constructor Called
Derived constructor called
Base Destructor called

A construção do objeto derivado seguir a regra de construção, mas quando nós excluir o "b" pointer (ponteiro base), descobrimos que somente o destruidor de base é chamado. Mas isso não deve ser acontecer. Para fazer a coisa apropriada, nós temos que fazer o destruidor 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 alterado da seguinte forma:

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

Assim, a destruição do ponteiro base (que leva uma alocação de objeto derivado!) Segue a regra destruição, i primeiro a Derivado, em seguida, o Base. Por outro lado, não há nada como um construtor virtual.

Declare destrutor virtual na classe base polimórficas. Esta é Item 7 em Scott Meyers' Effective C ++ . Meyers passa a resumir que, se uma classe tem qualquer função virtual, ele deve ter um destrutor virtual, e que as classes não foi concebido para ser classes base ou não concebido para ser utilizado polymorphically deve não declarar destruidores virtuais.

Também estar ciente de que a exclusão de um ponteiro de classe base quando não há nenhum processo de destruição virtual irá resultar em comportamento indefinido . Algo que eu aprendi recentemente:

Como deve substituir exclusão na comportam C ++?

Estou usando o C ++ durante anos e eu ainda conseguem me enforcar.

Faça o destruidor virtual sempre que sua classe é polimórfico.

Chamando destructor 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

chamada destrutor virtual não é diferente de qualquer outra chamada de função virtual.

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

O mesmo acontece quando destructor está sendo chamado indiretamente, por exemplo, delete base;. A declaração delete chamará base->~Base() que será enviado para Derived::~Derived().

classe abstrata com destructor não-virtual

Se você não está indo para objeto de exclusão através de um ponteiro para sua classe base - então não há necessidade de ter um destrutor virtual. Apenas torná-lo protected para que ele não será 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.
}

Eu gosto de pensar sobre interfaces e implementações de interfaces. Na interface de falar C ++ é de classe virtual pura. Destructor é parte da interface e espera-se implementado. Portanto destrutor deve ser puro virtual. Como cerca de Construtor? Construtor não é realmente parte da interface, porque objeto é sempre instanciado explicitamente.

Para ser simples, destrutor virtual é destruir os recursos em uma ordem adequada, quando você exclui um ponteiro de classe base apontando para objeto de classe derivada.

 #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

palavra-chave virtual para destructor é necessário quando você deseja diferentes destruidores devem seguir a ordem adequada, enquanto os objetos estão sendo eliminados através de ponteiro de 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 seu destruidor de classe derivada é virtual objetos, em seguida, será destrcuted em uma ordem (primeiro derivado objeto, em seguida base). Se o seu destruidor de classe derivada não é virtual, em seguida, objeto de classe única base vai ficar excluído (porque ponteiro é de classe base "Base * myObj"). Portanto, vai haver vazamento de memória para o objeto derivado.

O que é um destruidor virtual ou como usar destrutor virtual

A classe destruidor é uma função com o mesmo nome da classe anterior com ~ que irá realocar a memória que é atribuída pela classe. Por que precisamos de um processo de destruição virtual

Veja o exemplo a seguir, com algumas funções virtuais

A amostra também dizer como você pode converter uma carta ao 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;
}

A partir do exemplo acima você pode ver que o destruidor tanto para MakeUpper e classe 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 processo de destruição virtual vai chamar explicitamente o destrutor tempo de execução mais derivado de classe para que ele vai ser capaz de limpar o objeto de forma adequada.

Ou visite o link

https: // web.archive.org/web/20130822173509/http://www.programminggallery.com/article_details.php?article_id=138

Virtual destruidores classe base são "melhores práticas" - você deve sempre usá-los para evitar (difícil de detectar vazamentos de memória). Usá-los, você pode ter certeza todos os destruidores da cadeia de herança de suas classes são beeing chamados (na ordem correta). Herdar de uma classe base usando destrutor virtual torna o destruidor da classe herdar automaticamente virtual, também (assim você não precisa digitar novamente 'virtual' na declaração da classe destructor herdando).

Eu pensei que seria benéfico para discutir o comportamento "indefinido", ou pelo menos o "crash" comportamento indefinido que pode ocorrer ao excluir através de uma classe base (/ struct) sem um destrutor virtual, ou mais precisamente no vtable. O código abaixo lista algumas estruturas simples (o mesmo seria válido para 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;
*/
}

Eu não estou sugerindo se você precisa de destruidores virtuais ou não, embora eu acho que, em geral, é uma boa prática para tê-los. Estou apenas apontando a razão que você pode acabar com um acidente se sua classe base (/ struct) não tem uma vtable e sua classe derivada (/ struct) faz e você excluir um objeto através de uma classe base (/ struct) ponteiro. Neste caso, o endereço que você passar à rotina livre da pilha é inválida e, portanto, a razão para o acidente.

Se você executar o código acima, você vai ver claramente quando o problema ocorre. Quando a este ponteiro da classe base (/ struct) é diferente do este ponteiro da classe derivada (/ struct) que você está indo para executar para esse problema. No exemplo acima, struct a e b não tem vtables. estruturas C e D têm vtables. Assim, um a ou b ponteiro para um c ou d instância de objecto vai ser fixa-se a conta para a TabelaV. Se você passar este a ou b ponteiro para excluí-lo irá falhar devido ao endereço inválido sendo a rotina livre do heap.

Se você pretende excluir instâncias derivados que têm vtables de ponteiros de classe base, você precisa garantir que a classe base tem uma vtable. Uma maneira de fazer isso é adicionar um destrutor virtual, que você pode querer de qualquer maneira para recursos adequadamente limpos.

Eu acho que o núcleo desta questão é sobre os 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 virtual ele irá imprimir:

This is A.

E agora você deve entender quando usar destruidores virtuais.

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

Se você usar shared_ptr (somente shared_ptr, não unique_ptr), você não tem que ter o destruidor de 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());
}

saída:

Base Constructor Called
Derived constructor called
Derived destructor called
Base Destructor called

Qualquer classe que é herdada publicamente, polimórfica ou não, deve ter um destrutor virtual. Para colocar de outra forma, se ele pode ser apontada por um ponteiro de classe base, sua classe base deve ter um destrutor virtual.

Se virtual, o destruidor de classe derivada é chamado, em seguida, o construtor da classe base. Se não virtual, somente o destruidor de classe base é chamado.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top