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.

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

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

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


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.


4

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


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.


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.


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.



0

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


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


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;
  }
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.