¿Cuándo usar destructores virtuales?

1487

Tengo una sólida comprensión de la mayoría de la teoría OO, pero lo único que me confunde mucho son los 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é?

Lodle
fuente
66
Ver esto: Destructor virtual
Naveen
146
Cada destructor derribado es llamado sin importar qué. virtualse asegura de que comience en la parte superior en lugar de en el medio.
Mooing Duck
15
pregunta relacionada: ¿ Cuándo no debe usar destructores virtuales?
Eitan T
@MooingDuck es un comentario un tanto engañoso.
Euri Pinhollow
1
@FranklinYu es bueno que lo hayas preguntado porque ahora no puedo ver ningún problema con ese comentario (excepto tratar de responder en los comentarios).
Euri Pinhollow

Respuestas:

1573

Los destructores virtuales son útiles cuando podría 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á que no he declarado que el destructor de Base sea virtual. Ahora, echemos un vistazo al siguiente fragmento:

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

Desde destructor de base no está virtualy bes un Base*apuntador a un Derivedobjeto, delete btiene un comportamiento indefinido :

[En delete b], si el tipo estático del objeto a eliminar es diferente de su tipo dinámico, el tipo estático será una clase base del tipo dinámico del objeto a eliminar y el tipo estático tendrá un destructor virtual o 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 se llamará al destructor de la clase base pero no al de la clase derivada, lo que provocará una pérdida de recursos.

En resumen, siempre haga los destructores de las clases base virtualcuando 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 deletea un puntero de clase base.

Puede obtener más información sobre virtualidad y destructor de clase base virtual en este artículo de Herb Sutter .

Luc Touraille
fuente
174
Esto explicaría por qué tuve fugas masivas usando una fábrica que hice antes. Todo tiene sentido ahora. Gracias
Lodle
8
Bueno, este es un mal ejemplo ya que no hay miembros de datos. ¿Qué pasa si Basey Derivedtengo todas las variables de almacenamiento automático? es decir, no hay un código personalizado "especial" o adicional para ejecutar en el destructor. ¿Está bien dejar de escribir algún destructor? ¿O la clase derivada todavía tendrá una pérdida de memoria?
bobobobo
28
Del artículo de Herb Sutter: "Directriz # 4: Un destructor de clase base debe ser público y virtual, o protegido y no virtual".
Domingos
3
También del artículo: "si eliminas polimórficamente sin un destructor virtual, invocas el temido espectro del" comportamiento indefinido ", un espectro que personalmente preferiría no encontrar en un callejón moderadamente bien iluminado, muchas gracias". lol
Bondolin
219

Un constructor virtual no es posible pero sí es posible un destructor virtual. 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 genera 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 que el destructor base sea virtual. Ahora veamos qué sucede en lo 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

Entonces, la destrucción del puntero base (¡que toma una asignación en un objeto derivado!) Sigue la regla de destrucción, es decir, primero el Derivado, luego la Base. Por otro lado, no hay nada como un constructor virtual.

Tunvir Rahman Tusher
fuente
1
"constructor virtual no es posible" significa que no necesita escribir un constructor virtual por su cuenta. La construcción del objeto derivado debe seguir la cadena de construcción desde el derivado hasta la base. Por lo tanto, no necesita escribir la palabra clave virtual para su constructor. Gracias
Tunvir Rahman Tusher
44
@Murkantilism, "los constructores virtuales no se pueden hacer" es cierto. Un constructor no se puede marcar virtual.
cmeub
1
@cmeub, pero hay un modismo para lograr lo que desearías de un constructor virtual. Ver parashift.com/c++-faq-lite/virtual-ctors.html
cape1232
@TunvirRahmanTusher, ¿podría explicar por qué se llama al Destructor base?
rimalonfire
@rimiro Es automático por c ++. Puedes seguir el enlace stackoverflow.com/questions/677620/…
Tunvir Rahman Tusher
195

Declarar destructores virtuales en clases base polimórficas. Este es el ítem 7 en C ++ eficaz de Scott Meyers . Meyers continúa resumiendo que si una clase tiene alguna 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 usarse polimórficamente no deberían declarar destructores virtuales.

Bill el lagarto
fuente
14
+ "Si una clase tiene alguna 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 usarse polimórficamente no deberían declarar destructores virtuales": ¿Hay casos en los que tenga sentido romper esta regla? Si no, ¿tendría sentido que el compilador verifique esta condición y emita un error si no se cumple?
Giorgio
@ Jorge No conozco ninguna excepción a la regla. Pero no me calificaría como un experto en C ++, por lo que es posible que desee publicar esto como una pregunta separada. Una advertencia del compilador (o una advertencia de una herramienta de análisis estático) tiene sentido para mí.
Bill the Lizard
10
Las clases se pueden diseñar para que no se eliminen a través de un puntero de cierto tipo, pero aún tienen funciones virtuales; el ejemplo típico es una interfaz de devolución de llamada. Uno no elimina su implementación a través de un puntero de interfaz de devolución de llamada, ya que solo es para suscribirse, pero tiene funciones virtuales.
dascandy
3
Exactamente @dascandy - que o todas las muchas otras situaciones en las que utilizamos comportamiento polimórfico, pero no lleva a cabo la gestión de almacenamiento a través de punteros - por ejemplo, el mantenimiento de objetos automático o estático de duración, con punteros sólo se utilizan como rutas de observación. No es necesario / propósito implementar un destructor virtual en tales casos. Como solo estamos citando personas aquí, prefiero Sutter desde arriba: "Directriz # 4: Un destructor de clase base debe ser público y virtual, o protegido y no virtual". Este último garantiza que cualquier persona que intente eliminar accidentalmente a través de un puntero base muestre el error de sus formas
subrayado_d
1
@Giorgio En realidad, hay un truco que se puede usar y evitar una llamada virtual a un destructor: vincular mediante una referencia constante un objeto derivado a una base, como const Base& = make_Derived();. En este caso, se Derivedllamará al destructor del prvalue, incluso si no es virtual, por lo que se guarda la sobrecarga introducida por vtables / vpointers. Por supuesto, el alcance es bastante limitado. Andrei Alexandrescu mencionó esto en su libro Modern C ++ Design .
vsoftco
46

También tenga en cuenta que eliminar un puntero de clase base cuando no hay un destructor virtual dará como resultado un comportamiento indefinido . Algo que aprendí recientemente:

¿Cómo debe comportarse la eliminación de eliminar en C ++?

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

BigSandwich
fuente
Eché un vistazo a esa pregunta tuya y vi que habías declarado que el destructor base era virtual. Entonces, ¿"eliminar un puntero de clase base cuando no hay un destructor virtual resultará en un comportamiento indefinido" sigue siendo válido con respecto a esa pregunta tuya? Dado que, en esa pregunta, cuando llamó a delete, la clase derivada (creada por su nuevo operador) se verifica primero para una versión compatible. Como encontró uno allí, fue llamado. Entonces, ¿no crees que sería mejor decir que "eliminar un puntero de clase base cuando no hay destructor dará como resultado un comportamiento indefinido"?
ubuntugod
Eso es más o menos lo mismo. El constructor predeterminado no es virtual.
BigSandwich
41

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

yesraaj
fuente
13

Llamar a destructor mediante 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 de destructor virtual no es diferente de cualquier otra llamada de función virtual.

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

Lo mismo sucede cuando se llama al destructor indirectamente, por ejemplo delete base;. La deletedeclaración llamará base->~Base()y se enviará aDerived::~Derived() .

Clase abstracta con destructor no virtual

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. Solo hazlo protectedpara 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.
}
Abyx
fuente
¿Es necesario declarar explícitamente ~Derived()en todas las clases derivadas, incluso si es justo ~Derived() = default? ¿O está eso implícito en el lenguaje (haciendo que sea seguro omitirlo)?
Ponkadoodle
@Wallacoloo no, solo declararlo cuando sea necesario. Por ejemplo, para poner en la protectedsección, o para asegurarse de que es virtual mediante el uso override.
Abyx
9

Me gusta pensar en interfaces e implementaciones de interfaces. En C ++, la interfaz de hablar es pura clase virtual. Destructor es parte de la interfaz y se espera que se implemente. Por lo tanto, destructor debe ser puramente virtual. ¿Qué tal constructor? El constructor en realidad no es parte de la interfaz porque el objeto siempre se instancia explícitamente.

Dragan Ostojic
fuente
2
Es una perspectiva diferente sobre la misma pregunta. Si pensamos en términos de interfaces en lugar de clase base frente a clase derivada, entonces es una conclusión natural: si es parte de la interfaz, entonces conviértala en virtual. Si no es así, no lo hagas.
Dragan Ostojic
2
+1 para indicar la similitud del concepto de interfaz OO y una clase virtual pura C ++ . En cuanto al destructor se espera que se implemente : eso a menudo es innecesario. A menos que una clase administre un recurso como memoria bruta asignada dinámicamente (por ejemplo, no a través de un puntero inteligente), un identificador de archivo o un identificador de base de datos, el uso del destructor predeterminado creado por el compilador está bien en las clases derivadas. Y tenga en cuenta que si un destructor (o cualquier función) se declara virtualen una clase base, automáticamente se encuentra virtualen una clase derivada, incluso si no se declara así.
DavidRR
Esto pierde el detalle crucial de que el destructor no es necesariamente parte de la interfaz. Se pueden programar fácilmente clases que tienen funciones polimórficas pero que la persona que llama no gestiona / no puede eliminar. Entonces un destructor virtual no tiene ningún propósito. Por supuesto, para garantizar esto, el destructor no virtual, probablemente predeterminado, no debe ser público. Si tuviera que adivinar, diría que tales clases se usan más a menudo internamente para proyectos, pero eso no las hace menos relevantes como ejemplo / matiz en todo esto.
underscore_d
8

La palabra clave virtual para destructor es necesaria cuando desea que diferentes destructores sigan el orden correcto mientras los objetos se eliminan 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 el destructor de la clase base es virtual, los objetos se destruirán en un orden (primero el objeto derivado y luego la base). Si el destructor de la clase base NO es virtual, solo se eliminará el objeto de la clase base (porque el puntero es de la clase base "Base * myObj"). Entonces habrá pérdida de memoria para el objeto derivado.

Mukul Kashmira
fuente
7

Para ser simple, el destructor virtual es destruir los recursos en un orden adecuado, cuando 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

Prakash GiBBs
fuente
No tener el destructor virtual base y llamar deletea un puntero base conduce a un comportamiento indefinido.
James Adkison el
@JamesAdkison, ¿por qué conduce a un comportamiento indefinido?
rimalonfire
@rimiro Es lo que dice el estándar . No tengo una copia, pero el enlace lo lleva a un comentario donde alguien hace referencia a la ubicación dentro del estándar.
James Adkison el
@rimiro "Si la eliminación, por lo tanto, se puede realizar polimórficamente a través de la interfaz de la clase base, entonces debe comportarse virtualmente y debe ser virtual. De hecho, el lenguaje lo requiere: si elimina polimórficamente sin un destructor virtual, invoca el temido espectro de "comportamiento indefinido", un espectro que personalmente preferiría no conocer, incluso en un callejón moderadamente bien iluminado, muchas gracias ". ( gotw.ca/publications/mill18.htm ) - Herb Sutter
James Adkison
4

Los destructores de clase base virtuales son la "mejor práctica": siempre debe usarlos para evitar pérdidas de memoria (difíciles de detectar). 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 correcto). La herencia de una clase base que usa el destructor virtual hace que el destructor de la clase heredada también sea automáticamente virtual (por lo que no tiene que volver a escribir 'virtual' en la declaración del destructor de la clase heredadora).

Trantor
fuente
4

Si usa shared_ptr(solo shared_ptr, no unique_ptr), no tiene que tener el destructor de clase 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());
}

salida:

Base Constructor Called
Derived constructor called
Derived destructor called
Base Destructor called
Zhenxiao Hao
fuente
Aunque esto es posible, desalentaría a cualquiera de usar esto. La sobrecarga de un destructor virtual es minúscula y esto solo hace posible que se estropee, especialmente por un programador menos experimentado, que no lo sabe. Esa pequeña virtualpalabra clave podría salvarte de mucha agonía.
Michal Štein
3

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

Un destructor de clase es una función con el mismo nombre de la clase que precede a ~ 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 le dice cómo puede convertir una letra en mayúscula o minúscula

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

En el ejemplo anterior, puede ver que no se llama al destructor de las clases MakeUpper y MakeLower.

Vea 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 manera adecuada.

O visita el enlace

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

usuario2578542
fuente
2

cuando necesita llamar al destructor de clase derivada de la clase base. necesita declarar el destructor virtual de la clase base en la clase base.

usuario2641018
fuente
2

Creo que el núcleo de esta pregunta es sobre métodos virtuales y polimorfismo, no el destructor específicamente. Aquí hay 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;
}

Imprimirá:

This is B.

Sin virtualella se imprimirá:

This is A.

Y ahora debes entender cuándo usar destructores virtuales.

gonjay
fuente
No, esto solo recapitula los fundamentos básicos de las funciones virtuales, ignorando por completo el matiz de cuándo / por qué el destructor debería ser uno, lo que no es tan intuitivo, por lo tanto, el OP hizo la pregunta. (Además, ¿por qué la asignación dinámica innecesaria aquí? Simplemente hazlo B b{}; A& a{b}; a.foo();. No es necesario verificar NULL- lo que debería ser nullptr- antes deletede ingresar - con indendación incorrecta: delete nullptr;se define como no operativo. En todo caso, deberías haberlo verificado antes de llamar ->foo(), de lo contrario, puede ocurrir un comportamiento indefinido si de newalguna manera falla.)
underscore_d
2
Es seguro llamar deletea un NULLpuntero (es decir, no necesita la if (a != NULL)protección).
James Adkison el
@SaileshD Sí, lo sé. Eso es lo que dije en mi comentario
James Adkison el
1

Pensé que sería beneficioso discutir el comportamiento "indefinido", o al menos el comportamiento indefinido "bloqueado" que puede ocurrir al eliminar a través de una clase base (/ struct) sin un destructor virtual, o más precisamente, no hay vtable. El siguiente código enumera 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 necesita destructores virtuales o no, aunque creo que en general es una buena práctica tenerlos. Solo estoy señalando la razón por la que puede terminar con un bloqueo si su clase base (/ struct) no tiene una tabla vtable y su clase derivada (/ struct) sí y elimina un objeto a través de una clase base (/ struct) puntero. En este caso, la dirección que pasa a la rutina gratuita 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), se encontrará con este problema. En el ejemplo anterior, las estructuras ayb no tienen vtables. Las estructuras cyd tienen vtables. Por lo tanto, un puntero aob para una instancia de objeto ac o d se corregirá para dar cuenta de la tabla vtable. Si pasa este puntero aob para eliminarlo, 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 punteros de clase base, debe asegurarse de que la clase base tenga una vtable. Una forma de hacerlo es agregar un destructor virtual, que de todos modos es posible que desee limpiar adecuadamente los recursos.

nickdu
fuente
0

Una definición básica sobre virtuales si determina si una función miembro de una clase puede ser anulada en sus clases derivadas.

El D-tor de una clase se llama básicamente al final del alcance, pero hay un problema, por ejemplo, cuando definimos una instancia en el montón (asignación dinámica), deberíamos eliminarla manualmente.

Tan pronto como se ejecuta la instrucción, se llama al destructor de clase base, pero no para el derivado.

Un ejemplo práctico es cuando, en el campo de control, tienes que manipular efectores, actuadores.

Al final del alcance, si no se llama al destructor de uno de los elementos de poder (Actuador), habrá consecuencias fatales.

#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
fuente
-1

Cualquier clase que se herede públicamente, polimórfica o no, debe tener un destructor virtual. En otras palabras, si un puntero de clase base puede señalarlo, su clase base debería tener un destructor virtual.

Si es virtual, se llama al destructor de la clase derivada, luego al constructor de la clase base. Si no es virtual, solo se llama al destructor de la clase base.

Syed H
fuente
Yo diría que esto solo es necesario "si puede ser señalado por un puntero de clase base" y se puede eliminar públicamente. Pero supongo que no hace daño adquirir el hábito de agregar dtors virtuales en caso de que se necesiten más tarde.
underscore_d