¿Cuándo usar destructores virtuales?


Tengo una sólida comprensión de la mayoría de la teoría OO pero la única cosa que me confunde mucho es destructores virtuales.

Pensé que el destructor siempre se llama sin importar qué y para cada objeto en la cadena.

¿Cuándo debes hacerlos virtuales y por qué?

Author: j0k, 2009-01-20

15 answers

Los destructores virtuales son útiles cuando se puede eliminar una instancia de una clase derivada a través de un puntero a la clase base:

class Base 
{
    // some virtual methods
};

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

Aquí, notarán que no declaré que el destructor de la Base sea virtual. Ahora, echemos un vistazo al siguiente fragmento:

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

Dado que el destructor de Base no es virtual y b es un Base* que apunta a un objeto Derived, delete b tiene un comportamiento indefinido :

[En delete b], si el tipo estático de la objeto a eliminar es diferente de su tipo dinámico, el estático type será una clase base del tipo dinámico del objeto a ser deleted and the static type shall have a virtual destructor or the el comportamiento es indefinido .

En la mayoría de las implementaciones, la llamada al destructor se resolverá como cualquier código no virtual, lo que significa que el destructor de la clase base será llamado pero no el de la clase derivada, lo que resulta en una fuga de recursos.

Para resumir, siempre hacer destructores de clases base virtual cuando están destinados a ser manipulados polimórficamente.

Si desea evitar la eliminación de una instancia a través de un puntero de clase base, puede hacer que el destructor de clase base esté protegido y no sea virtual; al hacerlo, el compilador no le permitirá llamar a delete en un puntero de clase base.

Puedes aprender más sobre virtualidad y destructor de clase base virtual en este artículo de Herb Sutter.

 1340
Author: Luc Touraille,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-02-28 18:02:10

Declarar destructores virtuales en clases base polimórficas. Este es el Ítem 7 en el C++efectivo de Scott Meyers. Meyers continúa resumiendo que si una clase tiene cualquier función virtual, debería tener un destructor virtual, y que las clases no diseñadas para ser clases base o no diseñadas para ser usadas polimórficamente deberían no declarar destructores virtuales.

 168
Author: Bill the Lizard,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-01-20 13:11:51

Un constructor virtual no es posible, pero el destructor virtual es posible. Experimentemos....

#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;
}

El código anterior arroja lo siguiente:

Base Constructor Called
Derived constructor called
Base Destructor called

La construcción del objeto derivado sigue la regla de construcción, pero cuando eliminamos el puntero "b" (puntero base) hemos encontrado que solo se llama al destructor base.Pero esto no debe suceder. Para hacer lo apropiado tenemos que hacer virtual el destructor de la base. Ahora vamos a ver lo que sucede en el siguiente:

#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;
}

La salida cambió de la siguiente manera:

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

Así que la destrucción de puntero base (que toman una asignación en el objeto derivado!) siga la regla de destrucción es decir, primero la derivada y luego la base. Por otro lado para constructor no hay nada como constructor virtual.

 159
Author: Tunvir Rahman Tusher,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-03-31 09:50:41

También tenga en cuenta que la eliminación de un puntero de clase base cuando no hay destructor virtual dará lugar a comportamiento indefinido. Algo que aprendí hace poco:

¿Cómo se debe sobreescribir delete en C++?

He estado usando C++ durante años y todavía me las arreglo para ahorcarme.

 38
Author: BigSandwich,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:10:45

Haga virtual el destructor siempre que su clase sea polimórfica.

 30
Author: yesraaj,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-01-20 13:02:36

Llamando a destructor a través de un puntero a una clase 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

La llamada a destructor virtual no es diferente de cualquier otra llamada a función virtual.

Para base->f(), la llamada se enviará a Derived::f(), y es lo mismo para base->~Base() - su función de sobreescritura - se llamará a Derived::~Derived().

Lo mismo sucede cuando destructor está siendo llamado indirectamente, por ejemplo, delete base;. La declaración delete llamará a base->~Base(), que se enviará a Derived::~Derived().

Clase abstracta con no virtual destructor

Si no va a eliminar un objeto a través de un puntero a su clase base, entonces no es necesario tener un destructor virtual. Simplemente hazlo protected para que no se llame accidentalmente:

// 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.
}
 10
Author: Abyx,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-05-18 13:38:00

Me gusta pensar en interfaces e implementaciones de interfaces. En C++ hablar interfaz es pura clase virtual. Destructor es parte de la interfaz y se espera que implementado. Por lo tanto destructor debe ser pura virtual. ¿Constructor? Constructor en realidad no es parte de la interfaz porque el objeto siempre está instanciado explícitamente.

 7
Author: Dragan Ostojic,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-11-08 16:28:11

Para ser simple, Virtual destructor es destruir los recursos en un orden adecuado, cuando se elimina un puntero de clase base que apunta a un objeto de clase 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

 6
Author: Prakash GiBBs,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-08-26 05:33:24

La palabra clave virtual para destructor es necesaria cuando se desea que diferentes destructores sigan el orden adecuado mientras se eliminan los objetos a través del puntero de clase base. por ejemplo:

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

Si su destructor de clase derivada es virtual, entonces los objetos se destruirán en un orden(primero el objeto derivado y luego la base ). Si su destructor de clase derivada NO es virtual, entonces solo se eliminará el objeto de clase base (porque el puntero es de clase base "Base *miObj"). Así que habrá pérdida de memoria para objeto derivado.

 5
Author: Mukul Kashmira,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-01-29 07:11:34

Los destructores de clases base virtuales son "mejores prácticas" - siempre debe usarlos para evitar (difíciles de detectar) fugas de memoria. Al usarlos, puede estar seguro de que todos los destructores en la cadena de herencia de sus clases están siendo llamados (en el orden adecuado). Heredar de una clase base usando virtual destructor hace que el destructor de la clase heredera también sea virtual automáticamente (por lo que no tiene que volver a escribir 'virtual' en la declaración de destructor de clase heredera).

 2
Author: Trantor,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-07-17 07:44:24

Qué es un destructor virtual o cómo usar virtual destructor

Un destructor de clase es una función con el mismo nombre de la clase que precede con ~ que reasignará la memoria asignada por la clase. Por qué necesitamos un destructor virtual

Vea el siguiente ejemplo con algunas funciones virtuales

La muestra también indica cómo se puede convertir una letra a superior o 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;
}

De la muestra anterior se puede ver que el destructor tanto para MakeUpper y No se llama a la clase MakeLower.

Ver la siguiente muestra con el destructor 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;
}

El destructor virtual llamará explícitamente al destructor de tiempo de ejecución más derivado de la clase para que pueda borrar el objeto de una manera adecuada.

O visita el enlace

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

 1
Author: user2578542,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-01-29 19:33:49

Pensé que sería beneficioso discutir el comportamiento "indefinido", o al menos el comportamiento "crash" indefinido que puede ocurrir al eliminar a través de una clase base(/struct) sin un destructor virtual, o más precisamente sin vtable. El siguiente código lista algunas estructuras simples (lo mismo sería cierto para las clases).

#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;
*/
}

No estoy sugiriendo si se necesitan destructores virtuales o no, aunque creo que en general es una buena práctica tenerlos. Sólo estoy señalando la razón por la que puede terminar con un bloqueo si su clase base(/struct) no tiene una vtable y su clase derivada(/struct) sí y elimina un objeto a través de un puntero de clase base (/struct). En este caso, la dirección que pasa a la rutina libre del montón no es válida y, por lo tanto, la razón del bloqueo.

Si ejecuta el código anterior, verá claramente cuándo se produce el problema. Cuando el puntero this de la clase base (/struct) es diferente del puntero this de la clase derivada (/struct) vas a ejecutar en este problema. En el ejemplo anterior, la estructura a y b no tienen vtables. las estructuras c y d tienen vtables. Por lo tanto, un puntero a o b a una instancia de objeto c o d se arreglará para tener en cuenta la vtable. Si pasa este puntero a o b para eliminar, se bloqueará debido a que la dirección no es válida para la rutina libre del montón.

Si planea eliminar instancias derivadas que tienen vtables de los punteros de clase base, debe asegurarse de que la clase base tenga una vtable. Una forma de hacerlo es agregar un destructor virtual, que puede que quieras de todos modos para limpiar correctamente los recursos.

 1
Author: nickdu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-03-23 18:22:30

Cuando necesita llamar a la clase derivada destructor desde la clase base. es necesario declarar virtual base class destructor en la clase base.

 0
Author: user2641018,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-09-09 07:02:02

Creo que el núcleo de esta pregunta es acerca de los métodos virtuales y el polimorfismo, no el destructor específicamente. He aquí un ejemplo más 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;
}

Se imprimirá:

This is B.

Sin virtual se imprimirá:

This is A.

Y ahora deberías entender cuándo usar destructores virtuales.

 0
Author: gonjay,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-07-23 09:49:18

Cualquier clase que se hereda públicamente, polimórfica o no, debe tener un destructor virtual. Para decirlo de otra manera, si puede ser apuntado por un puntero de clase base, su clase base debe tener un destructor virtual.

Si es virtual, el destructor de clase derivado es llamado, entonces el constructor de clase base. Si no es virtual, solo se llama al destructor de la clase base.

 -1
Author: Syed H,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-01-02 16:27:51