L'eliminazione su un puntatore a una sottoclasse chiama il distruttore della classe base?


165

Ne ho uno class Ache utilizza un'allocazione di memoria heap per uno dei suoi campi. La classe A è istanziata e memorizzata come campo puntatore in un'altra classe ( class B.

Quando ho finito con un oggetto di classe B, chiamo delete, che presumo chiama il distruttore ... Ma questo chiama anche il distruttore di classe A?

Modificare:

Dalle risposte, lo prendo (per favore modifica se non corretto):

  1. delete di un'istanza di B chiama B :: ~ B ();
  2. che chiama A::~A();
  3. A::~A dovrebbe esplicitamente deletetutte le variabili membro allocate in heap dell'oggetto A;
  4. Alla fine il blocco di memoria che memorizza detta istanza di classe B viene restituito all'heap: quando ne veniva usato uno nuovo , allocava prima un blocco di memoria sull'heap, quindi invocava i costruttori per inizializzarlo, ora dopo che tutti i distruttori sono stati richiamati per finalizzare l'oggetto blocco in cui l'oggetto risiedeva viene restituito all'heap.

Risposte:


183

Il distruttore di A verrà eseguito alla fine della sua vita. Se si desidera liberare la memoria e far funzionare il distruttore, è necessario eliminarlo se è stato allocato sull'heap. Se è stato allocato nello stack, ciò avviene automaticamente (ovvero quando esce dal campo di applicazione; vedere RAII). Se è un membro di una classe (non un puntatore, ma un membro completo), ciò accadrà quando l'oggetto contenente viene distrutto.

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

Nell'esempio precedente, è necessario eliminare e cancellare []. E nessuna cancellazione è necessaria (o effettivamente utilizzabile) dove non l'ho usata.

auto_ptr, unique_ptrE shared_ptrecc ... sono grande per fare questa gestione vita molto più semplice:

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

Mi chiedo se viene chiamato il distruttore quando si libera la memoria solo parzialmente (es. Usando un puntatore sbagliato)
Tomáš Zato - Reinstate Monica il

Il puntatore è solo un numero. È anche possibile utilizzare accidentalmente l' ++operatore su di esso. Quindi mi chiedo se il puntatore che punta nel mezzo dei dati della classe abbia ancora l'effetto.
Tomáš Zato - Ripristina Monica il

2
@ TomášZato: se chiami cancella su un puntatore casuale, allora sei fregato. Non c'è mai una buona ragione per farlo. In effetti, se stai chiamando manualmente l'eliminazione in un punto diverso da un distruttore smart-pointer, probabilmente vorrai dare una seconda occhiata al motivo per cui non stai utilizzando un puntatore smart o qualche altro gestore oggetti.
Eclipse,

shared_array proviene solo da boost, sì?
Dronz,

30

Quando si chiama delete su un puntatore allocato da new, verrà chiamato il distruttore dell'oggetto indicato.

A * p = new A;

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

22

Si chiama "distruttore", non "decostruttore".

All'interno del distruttore di ogni classe, devi eliminare tutte le altre variabili membro che sono state allocate con nuove.

modifica: per chiarire:

Di 'che hai

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

Allocare un'istanza di B e quindi eliminare è pulito, perché anche ciò che B alloca internamente verrà eliminato nel distruttore.

Ma le istanze di classe C perdono memoria, perché alloca un'istanza di A che non rilascia (in questo caso C non ha nemmeno un distruttore).


5

Se hai un normale puntatore ( A*), il distruttore non verrà chiamato (e Aneanche la memoria, ad esempio, verrà liberata) a meno che tu non lo faccia deleteesplicitamente nel Bdistruttore. Se vuoi la distruzione automatica, guarda come i puntatori intelligenti auto_ptr.


4

Devi eliminare A tu stesso nel distruttore di 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;
    }
};

Quando lo fai:

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

Il distruttore verrà chiamato solo se la tua classe base ha la parola chiave virtuale.

Quindi se non avessi un distruttore virtuale, verrebbe chiamato solo ~ B (). Ma dal momento che hai un distruttore virtuale, prima verrà chiamato ~ D (), quindi ~ B ().

Nessun membro di B o D allocato nell'heap verrà deallocato a meno che non venga eliminato esplicitamente. E la loro eliminazione chiamerà anche il loro distruttore.


1

Hai qualcosa del genere

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

Se poi chiami delete b;, non succede nulla a e hai una perdita di memoria. Cercare di ricordare delete b->a;non è una buona soluzione, ma ce ne sono un paio.

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

Questo è un distruttore per B che cancellerà a. (Se a è 0, quell'eliminazione non fa nulla. Se a non è 0 ma non punta alla memoria da nuova, si ottiene la corruzione dell'heap.)

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

In questo modo non hai un puntatore come, ma piuttosto un auto_ptr <> (lo farà anche altrettanto_ptr <>, o altri puntatori intelligenti), e viene automaticamente cancellato quando b lo è.

Entrambi questi modi funzionano bene e ho usato entrambi.


1

Mi chiedevo perché il distruttore della mia classe non fosse chiamato. Il motivo era che avevo dimenticato di includere la definizione di quella classe (#include "class.h"). Avevo solo una dichiarazione come "classe A;" e il compilatore ne era contento e fammi chiamare "elimina".


Aumenta il livello di avviso del compilatore
Phil1970,

0

No. il puntatore verrà eliminato. Dovresti chiamare la cancellazione su A esplicito nel distruttore di B.


Lo sto facendo, la mia domanda era: si chiama il distruttore?
Nick Bolton,


0

no, non chiamerà distruttore per la classe A, dovresti chiamarlo esplicitamente (come disse PoweRoy), eliminare la riga "delete ptr;" ad esempio per confrontare ...

  #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;
  }
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.