Czy usuwanie po wskaźniku do podklasy wywołuje destruktor klasy bazowej?

165

Mam, class Aktóry używa alokacji pamięci sterty dla jednego ze swoich pól. Klasa A jest tworzona i przechowywana jako pole wskaźnika w innej klasie ( class B.

Kiedy skończę z obiektem klasy B, wzywam delete, który, jak przypuszczam, wywołuje destruktor… Ale czy to wywołuje również destruktor klasy A?

Edytować:

Z odpowiedzi wyciągam to (edytuj, jeśli są nieprawidłowe):

  1. delete instancji B wywołuje B :: ~ B ();
  2. który dzwoni A::~A();
  3. A::~A powinny jawnie deletewszystkie przypisane do sterty zmienne składowe obiektu A;
  4. Na koniec blok pamięci przechowujący wspomnianą instancję klasy B jest zwracany na stertę - gdy nowy został użyty, najpierw przydzielił blok pamięci na stercie, a następnie wywołał konstruktory, aby go zainicjować, teraz po wywołaniu wszystkich destruktorów w celu sfinalizowania obiektu blok, w którym rezydował obiekt, jest zwracany na stertę.
Nick Bolton
źródło

Odpowiedzi:

183

Destruktor A będzie działał po zakończeniu jego życia. Jeśli chcesz zwolnić jego pamięć i uruchomić destruktor, musisz go usunąć, jeśli został przydzielony na stercie. Jeśli został przydzielony na stosie, dzieje się to automatycznie (tj. Gdy wychodzi poza zakres; patrz RAII). Jeśli jest członkiem klasy (nie wskaźnikiem, ale pełnym składnikiem), to nastąpi, gdy obiekt zawierający zostanie zniszczony.

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

W powyższym przykładzie potrzebne jest każde usunięcie i usunięcie []. I żadne usuwanie nie jest potrzebne (lub rzeczywiście można go użyć), jeśli go nie użyłem.

auto_ptr, unique_ptrI shared_ptretc ... są idealne do podejmowania tego zarządzania dożywotnią znacznie łatwiejsze:

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
Zaćmienie
źródło
Zastanawiam się, czy destruktor jest wywoływany, gdy zwolnisz pamięć tylko częściowo (np. Używając złego wskaźnika)
Tomáš Zato - Przywróć Monikę
Wskaźnik to tylko liczba. Możesz nawet przypadkowo użyć ++na nim operatora. Zastanawiam się więc, czy wskaźnik, który wskazuje środek danych klasy, nadal działa.
Tomáš Zato - Przywróć Monikę
2
@ TomášZato: Jeśli wywołasz funkcję delete na przypadkowym wskaźniku, masz przerąbane. Nigdy nie ma dobrego powodu, aby to robić. W rzeczywistości, jeśli ręcznie wywołujesz usuwanie w dowolnym miejscu innym niż destruktor inteligentnego wskaźnika, prawdopodobnie zechcesz ponownie przyjrzeć się, dlaczego nie używasz inteligentnego wskaźnika lub innego menedżera obiektów.
Eclipse
shared_array pochodzi tylko z boostu, tak?
Dronz
30

Kiedy wywołujesz delete na wskaźniku przydzielonym przez new, zostanie wywołany destruktor obiektu wskazywanego.

A * p = new A;

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

źródło
22

Nazywa się „destruktorem”, a nie „dekonstruktorem”.

Wewnątrz destruktora każdej klasy musisz usunąć wszystkie inne zmienne składowe, którym przydzielono nowe.

edytuj: Aby wyjaśnić:

Powiedz, że masz

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

Przydzielenie wystąpienia B, a następnie usunięcie jest czyste, ponieważ to, co B przydzieli wewnętrznie, zostanie również usunięte w destruktorze.

Ale instancje klasy C będą przeciekać pamięć, ponieważ przydziela instancję A, której nie zwalnia (w tym przypadku C nie ma nawet destruktora).

Sebastian Mach
źródło
5

Jeśli masz zwykły wskaźnik ( A*), to destruktor nie zostanie wywołany (i na Aprzykład pamięć też nie zostanie zwolniona), chyba że zrobisz to deletejawnie w Bdestruktorze. Jeśli chcesz automatycznego niszczenia, spójrz na inteligentne wskazówki, takie jak auto_ptr.

ostry
źródło
4

Powinieneś samodzielnie usunąć A w destruktorze B.

corné
źródło
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;
    }
};

Kiedy robisz:

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

Destruktor zostanie wywołany tylko wtedy, gdy twoja klasa bazowa zawiera słowo kluczowe virtual.

Wtedy, gdybyś nie miał wirtualnego destruktora, wywołano by tylko ~ B (). Ale ponieważ masz wirtualny destruktor, najpierw zostanie wywołana ~ D (), a następnie ~ B ().

Żaden członek B lub D przydzielony na stercie nie zostanie zwolniony, chyba że zostanie ich jawnie usunięty. Usunięcie ich spowoduje również wywołanie ich destruktora.

Brian R. Bondy
źródło
1

Masz coś takiego

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

Jeśli następnie zadzwonisz delete b;, nic się nie stanie i masz wyciek pamięci. Próba zapamiętania delete b->a;nie jest dobrym rozwiązaniem, ale jest kilka innych.

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

To jest destruktor dla B, który usunie plik. (Jeśli a jest równe 0, to usunięcie nic nie daje. Jeśli a nie jest równe 0, ale nie wskazuje na pamięć z nowego, otrzymujesz uszkodzenie sterty).

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

W ten sposób nie masz a jako wskaźnika, ale raczej auto_ptr <> (shared_ptr <> lub inne inteligentne wskaźniki) i jest automatycznie usuwane, gdy b jest.

Każdy z tych sposobów działa dobrze i użyłem obu.

David Thornley
źródło
1

Zastanawiałem się, dlaczego nie wywołano destruktora mojej klasy. Powodem było to, że zapomniałem dołączyć definicję tej klasy (#include "class.h"). Miałem tylko deklarację typu „klasa A”; a kompilator był z tego zadowolony i pozwól mi zadzwonić do "delete".

Harri Luoma
źródło
Zwiększ poziom ostrzeżenia kompilatora
Phil1970,
0

Nie. Wskaźnik zostanie usunięty. Powinieneś wywołać delete na A explicite w destruktorze B.

RvdK
źródło
Robię to, moje pytanie brzmi: czy nazywa się destruktor?
Nick Bolton,
0

Destruktor obiektu klasy A zostanie wywołany tylko wtedy, gdy dla tego obiektu zostanie wywołane usuwanie. Pamiętaj, aby usunąć ten wskaźnik w destruktorze klasy B.

Aby uzyskać więcej informacji na temat tego, co dzieje się, gdy wywoływane jest usuwanie na obiekcie, zobacz: http://www.parashift.com/c++-faq-lite/freestore-mgmt.html#faq-16.9

Kris Kumler
źródło
0

nie, nie wywoła destruktora dla klasy A, powinieneś wywołać go jawnie (jak powiedział PoweRoy), usuń linię 'delete ptr;' na przykład, aby porównać ...

  #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
źródło