Ruft das Löschen eines Zeigers auf eine Unterklasse den Destruktor der Basisklasse auf?

165

Ich habe eine, class Adie eine Heap-Speicherzuordnung für eines ihrer Felder verwendet. Klasse A wird instanziiert und als Zeigerfeld in einer anderen Klasse gespeichert ( class B.

Wenn ich mit einem Objekt der Klasse B fertig bin, rufe ich auf delete, von dem ich annehme, dass es den Destruktor aufruft ... Aber ruft dies auch den Destruktor der Klasse A auf?

Bearbeiten:

Aus den Antworten nehme ich das (bitte bearbeiten, wenn falsch):

  1. delete einer Instanz von B ruft B :: ~ B () auf;
  2. was anruft A::~A();
  3. A::~A sollte explizit deletealle Heap-zugewiesenen Mitgliedsvariablen des A-Objekts enthalten;
  4. Schließlich wird der Speicherblock, in dem die Instanz der Klasse B gespeichert ist, an den Heap zurückgegeben. Wenn new verwendet wurde, hat er zuerst einen Speicherblock auf dem Heap zugewiesen und dann Konstruktoren aufgerufen, um ihn zu initialisieren, nachdem alle Destruktoren aufgerufen wurden, um das Objekt zu finalisieren Block, in dem sich das Objekt befand, wird an den Heap zurückgegeben.
Nick Bolton
quelle

Antworten:

183

Der Destruktor von A wird ausgeführt, wenn seine Lebensdauer abgelaufen ist. Wenn der Speicher freigegeben und der Destruktor ausgeführt werden soll, müssen Sie ihn löschen, wenn er auf dem Heap zugewiesen wurde. Wenn es auf dem Stapel zugewiesen wurde, geschieht dies automatisch (dh wenn es außerhalb des Gültigkeitsbereichs liegt; siehe RAII). Wenn es sich um ein Mitglied einer Klasse handelt (kein Zeiger, sondern ein vollständiges Mitglied), geschieht dies, wenn das enthaltende Objekt zerstört wird.

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

Im obigen Beispiel wird jedes Löschen und Löschen [] benötigt. Und es ist kein Löschen erforderlich (oder kann tatsächlich verwendet werden), wenn ich es nicht verwendet habe.

auto_ptr, unique_ptrund shared_ptretc ... sind großartig, um dieses lebenslange Management viel einfacher zu machen:

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
Finsternis
quelle
Ich frage mich, ob Destruktor aufgerufen wird, wenn Sie den Speicher nur teilweise freigeben (z. B. mit falschem Zeiger)
Tomáš Zato - Monica
Zeiger ist nur eine Zahl. Sie können sogar versehentlich den ++Operator verwenden. Ich frage mich also, ob ein Zeiger, der in die Mitte der Klassendaten zeigt, noch den Effekt hat.
Tomáš Zato - Wiedereinsetzung Monica
2
@ TomášZato: Wenn Sie delete auf einem zufälligen Zeiger aufrufen, dann sind Sie geschraubt. Es gibt nie einen guten Grund, das zu tun. Wenn Sie delete an einer anderen Stelle als einem Smart-Pointer-Destruktor manuell aufrufen, möchten Sie wahrscheinlich einen zweiten Blick darauf werfen, warum Sie keinen Smart-Pointer oder einen anderen Objektmanager verwenden.
Eclipse
shared_array ist nur von boost, ja?
Dronz
30

Wenn Sie delete für einen von new zugewiesenen Zeiger aufrufen, wird der Destruktor des Objekts aufgerufen, auf das verwiesen wird.

A * p = new A;

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

quelle
22

Es heißt "Destruktor", nicht "Dekonstruktor".

Innerhalb des Destruktors jeder Klasse müssen Sie alle anderen Mitgliedsvariablen löschen, denen new zugewiesen wurde.

bearbeiten: Zur Verdeutlichung:

Sagen Sie, Sie haben

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

Das Zuweisen einer Instanz von B und das anschließende Löschen ist sauber, da das, was B intern zuweist, auch im Destruktor gelöscht wird.

Instanzen der Klasse C verlieren jedoch Speicher, da sie eine Instanz von A zuweisen, die sie nicht freigibt (in diesem Fall hat C nicht einmal einen Destruktor).

Sebastian Mach
quelle
5

Wenn Sie einen üblichen Zeiger ( A*) haben, wird der Destruktor nicht aufgerufen (und Abeispielsweise wird auch kein Speicher freigegeben), es sei denn, Sie tun dies deleteexplizit im BDestruktor. Wenn Sie automatische Zerstörung wollen, schauen Sie sich intelligente Zeiger wie an auto_ptr.

scharfer Zahn
quelle
4

Sie sollten A selbst im Destruktor von B löschen.

corné
quelle
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;
    }
};

Wenn Sie das tun:

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

Der Destruktor wird nur aufgerufen, wenn Ihre Basisklasse das virtuelle Schlüsselwort hat.

Wenn Sie dann keinen virtuellen Destruktor hätten, würde nur ~ B () aufgerufen. Da Sie jedoch einen virtuellen Destruktor haben, wird zuerst ~ D () und dann ~ B () aufgerufen.

Auf dem Heap zugewiesene Mitglieder von B oder D werden nicht freigegeben, es sei denn, Sie löschen sie ausdrücklich. Wenn Sie sie löschen, wird auch ihr Destruktor aufgerufen.

Brian R. Bondy
quelle
1

Du hast so etwas wie

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

Wenn Sie dann anrufen delete b;, passiert nichts mit a und Sie haben einen Speicherverlust. Der Versuch, sich daran zu erinnern, delete b->a;ist keine gute Lösung, aber es gibt noch einige andere.

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

Dies ist ein Destruktor für B, der a löscht. (Wenn a 0 ist, bewirkt dieses Löschen nichts. Wenn a nicht 0 ist, aber nicht auf den Speicher von new verweist, wird der Heap beschädigt.)

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

Auf diese Weise haben Sie a nicht als Zeiger, sondern ein auto_ptr <> (shared_ptr <> oder andere intelligente Zeiger) und es wird automatisch gelöscht, wenn b ist.

Jede dieser Möglichkeiten funktioniert gut, und ich habe beide verwendet.

David Thornley
quelle
1

Ich fragte mich, warum der Zerstörer meiner Klasse nicht gerufen wurde. Der Grund war, dass ich vergessen hatte, die Definition dieser Klasse aufzunehmen (#include "class.h"). Ich hatte nur eine Erklärung wie "Klasse A"; und der Compiler war damit zufrieden und ließ mich "löschen" nennen.

Harri Luoma
quelle
Erhöhen Sie die
0

Nein, der Zeiger wird gelöscht. Sie sollten das Löschen auf A explizit im Destruktor von B aufrufen.

RvdK
quelle
Ich mache das, meine Frage war, heißt der Destruktor?
Nick Bolton
0

Der Destruktor für das Objekt der Klasse A wird nur aufgerufen, wenn für dieses Objekt delete aufgerufen wird. Stellen Sie sicher, dass Sie diesen Zeiger im Destruktor der Klasse B löschen.

Weitere Informationen darüber, was passiert, wenn ein Objekt gelöscht wird, finden Sie unter: http://www.parashift.com/c++-faq-lite/freestore-mgmt.html#faq-16.9

Kris Kumler
quelle
0

Nein, es wird kein Destruktor für Klasse A aufgerufen. Sie sollten ihn explizit aufrufen (wie PoweRoy sagte). Löschen Sie die Zeile 'delete ptr;' im Beispiel zu vergleichen ...

  #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
quelle