¿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.
Nick Bolton
fuente

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
Eclipse
fuente
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

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

Sebastian Mach
fuente
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.

diente filoso
fuente
4

Debe eliminar A usted mismo en el destructor de B.

corné
fuente
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.

Brian R. Bondy
fuente
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.

David Thornley
fuente
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".

Harri Luoma
fuente
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.

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

El destructor para el objeto de la clase A solo se llamará si se llama a delete para ese objeto. Asegúrese de eliminar ese puntero en el destructor de la clase B.

Para obtener un poco más de información sobre lo que sucede cuando se invoca eliminar en un objeto, consulte: http://www.parashift.com/c++-faq-lite/freestore-mgmt.html#faq-16.9

Kris Kumler
fuente
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;
  }
Darius Kucinskas
fuente