¿Eliminar en un puntero a una subclase llama al destructor de la clase base?


165

Tengo uno class Aque usa una asignación de memoria de montón para uno de sus campos. La clase A se instancia y se almacena como un campo de puntero en otra clase ( class B.

Cuando termino con un objeto de clase B, llamo delete, que supongo que llama al destructor ... ¿Pero esto también llama al destructor de la clase A?

Editar:

De las respuestas, tomo eso (edite si es incorrecto):

  1. delete de una instancia de B llama a B :: ~ B ();
  2. que llama A::~A();
  3. A::~A debería explícitamente deletetodas las variables miembro asignadas en el montón del objeto A;
  4. Finalmente, el bloque de memoria que almacena dicha instancia de clase B se devuelve al montón: cuando se usó nuevo , primero asignó un bloque de memoria en el montón, luego invocó a los constructores para inicializarlo, ahora después de que se hayan invocado todos los destructores para finalizar el objeto, el El bloque donde residía el objeto se devuelve al montón.

Respuestas:


183

El destructor de A se ejecutará cuando termine su vida útil. Si desea que se libere su memoria y se ejecute el destructor, debe eliminarlo si se asignó en el montón. Si se asignó en la pila, esto sucede automáticamente (es decir, cuando se sale del alcance; ver RAII). Si es un miembro de una clase (no un puntero, sino un miembro completo), esto sucederá cuando se destruya el objeto que lo contiene.

class A
{
    char *someHeapMemory;
public:
    A() : someHeapMemory(new char[1000]) {}
    ~A() { delete[] someHeapMemory; }
};

class B
{
    A* APtr;
public:
    B() : APtr(new A()) {}
    ~B() { delete APtr; }
};

class C
{
    A Amember;
public:
    C() : Amember() {}
    ~C() {} // A is freed / destructed automatically.
};

int main()
{
    B* BPtr = new B();
    delete BPtr; // Calls ~B() which calls ~A() 
    C *CPtr = new C();
    delete CPtr;
    B b;
    C c;
} // b and c are freed/destructed automatically

En el ejemplo anterior, se necesita eliminar y eliminar []. Y no se necesita eliminar (o de hecho se puede usar) donde no lo usé.

auto_ptr, unique_ptry shared_ptretc ... son excelentes para hacer esta gestión de por vida mucho más fácil:

class A
{
    shared_array<char> someHeapMemory;
public:
    A() : someHeapMemory(new char[1000]) {}
    ~A() { } // someHeapMemory is delete[]d automatically
};

class B
{
    shared_ptr<A> APtr;
public:
    B() : APtr(new A()) {}
    ~B() {  } // APtr is deleted automatically
};

int main()
{
    shared_ptr<B> BPtr = new B();
} // BPtr is deleted automatically

Me pregunto si destructor se invoca cuando se libera la memoria sólo parcialmente (por ejemplo, utilizando el puntero equivocado.)
Tomáš Zato - Restablecer Mónica

El puntero es solo un número. Incluso puede usar accidentalmente el ++operador en él. Entonces, me pregunto si el puntero que apunta en el medio de los datos de la clase todavía tiene el efecto.
Tomáš Zato - Restablece a Mónica el

2
@ TomášZato: Si llamas a eliminar en un puntero aleatorio, entonces estás jodido. Nunca hay una buena razón para estar haciendo eso. De hecho, si está llamando manualmente a eliminar en cualquier lugar que no sea un destructor de puntero inteligente, entonces probablemente desee echar un segundo vistazo a por qué no está utilizando un puntero inteligente u otro administrador de objetos.
Eclipse

shared_array es solo de boost, ¿sí?
Dronz

30

Cuando llame a delete en un puntero asignado por new, se llamará al destructor del objeto señalado.

A * p = new A;

delete p;    // A:~A() called for you on obkect pointed to by p

22

Se llama "destructor", no "deconstructor".

Dentro del destructor de cada clase, debe eliminar todas las demás variables miembro que se han asignado con nuevo.

editar: Para aclarar:

Di que tienes

struct A {}

class B {
    A *a;
public:
    B () : a (new A) {}
    ~B() { delete a; }
};

class C {
    A *a;
public:
    C () : a (new A) {}        
};

int main () {
    delete new B;
    delete new C;
}

Asignar una instancia de B y luego eliminar es limpio, porque lo que B asigna internamente también se eliminará en el destructor.

Pero las instancias de la clase C perderán memoria, porque asigna una instancia de A que no libera (en este caso, C ni siquiera tiene un destructor).


5

Si tiene un puntero habitual ( A*), no se llamará al destructor (y la memoria, por Aejemplo, tampoco se liberará) a menos que lo haga deleteexplícitamente en Bel destructor. Si quieres la destrucción automática, mira los punteros inteligentes como auto_ptr.


4

Debe eliminar A usted mismo en el destructor de B.


4
class B
{
public:
    B()
    {
       p = new int[1024];  
    }
    virtual ~B()
    {
        cout<<"B destructor"<<endl;
        //p will not be deleted EVER unless you do it manually.
    }
    int *p;
};


class D : public B
{
public:
    virtual ~D()
    {
        cout<<"D destructor"<<endl;
    }
};

Cuando tu lo hagas:

B *pD = new D();
delete pD;

Se llamará al destructor solo si su clase base tiene la palabra clave virtual.

Entonces, si no tuviera un destructor virtual, solo se llamaría ~ B (). Pero como tiene un destructor virtual, primero se llamará a ~ D (), luego a ~ B ().

Ningún miembro de B o D asignado en el montón se desasignará a menos que los elimine explícitamente. Y eliminarlos también llamará a su destructor.


1

Tienes algo como

class B
{
   A * a;
}
B * b = new B;
b->a = new A;

Si luego llama delete b;, no le pasa nada a, y tiene una pérdida de memoria. Intentar recordar delete b->a;no es una buena solución, pero hay un par de otras.

B::~B() {delete a;}

Este es un destructor para B que eliminará a. (Si a es 0, esa eliminación no hace nada. Si a no es 0 pero no apunta a la memoria de nuevo, se daña el montón).

auto_ptr<A> a;
...
b->a.reset(new A);

De esta manera, no tiene un puntero, sino un auto_ptr <> (shared_ptr <> también lo hará, u otros punteros inteligentes), y se elimina automáticamente cuando b lo es.

Cualquiera de estas formas funciona bien, y he usado ambas.


1

Me preguntaba por qué no se llamó al destructor de mi clase. La razón fue que olvidé incluir la definición de esa clase (#include "class.h"). Solo tenía una declaración como "clase A"; y el compilador estaba contento con él y me dejó llamar "borrar".


Aumentar el nivel de advertencia del compilador
Phil1970

0

No. el puntero se eliminará. Debe llamar a la eliminación en A explícita en el destructor de B.


Estoy haciendo esto, mi pregunta fue ¿se llama el destructor?
Nick Bolton


0

no, no llamará al destructor para la clase A, debe llamarlo explícitamente (como le dijo PoweRoy), elimine la línea 'delete ptr;' en ejemplo para comparar ...

  #include <iostream>

  class A
  {
     public:
        A(){};
        ~A();
  };

  A::~A()
  {
     std::cout << "Destructor of A" << std::endl;
  }

  class B
  {
     public:
        B(){ptr = new A();};
        ~B();
     private:
        A* ptr;
  };

  B::~B()
  {
     delete ptr;
     std::cout << "Destructor of B" << std::endl;
  }

  int main()
  {
     B* b = new B();
     delete b;
     return 0;
  }
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.