Excluir em um ponteiro para uma subclasse chama o destruidor da classe base?

165

Eu tenho um class Aque usa uma alocação de memória heap para um de seus campos. A classe A é instanciada e armazenada como um campo de ponteiro em outra classe ( class B.

Quando termino com um objeto da classe B, eu chamo delete, que eu suponho que chama de destruidor ... Mas isso também chama o destruidor da classe A?

Editar:

A partir das respostas, entendi (por favor, edite se estiver incorreto):

  1. delete de uma instância de B chama B :: ~ B ();
  2. que chama A::~A();
  3. A::~A deve explicitamente deletetodas as variáveis de membro alocado-heap do objeto A;
  4. Finalmente, o bloco de memória que armazena a instância da classe B é retornada ao heap - quando novo foi usado, ele primeiro alocou um bloco de memória no heap e depois chamou os construtores para inicializá-lo, agora que todos os destruidores foram chamados para finalizar o objeto. bloco em que o objeto residia é retornado ao heap.
Nick Bolton
fonte

Respostas:

183

O destruidor de A será executado quando a vida útil terminar. Se você deseja que sua memória seja liberada e o destruidor seja executado, você deve excluí-la se ela estiver alocada no heap. Se ele foi alocado na pilha, isso acontece automaticamente (ou seja, quando fica fora do escopo; consulte RAII). Se for um membro de uma classe (não um ponteiro, mas um membro completo), isso acontecerá quando o objeto contido for destruído.

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

No exemplo acima, cada exclusão e exclusão [] são necessárias. E nenhuma exclusão é necessária (ou realmente pode ser usada) onde eu não a usei.

auto_ptr, unique_ptre shared_ptretc ... são ótimos para facilitar esse gerenciamento vitalício:

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
fonte
Gostaria de saber se o destruidor é chamado quando você libera a memória apenas parcialmente (por exemplo, usando o ponteiro errado)
Tomáš Zato - Restabelece Monica
Ponteiro é apenas um número. Você pode usar acidentalmente o ++operador nele. Então, eu me pergunto se o ponteiro que aponta no meio dos dados da classe ainda tem o efeito.
Tomáš Zato - Restabelece Monica
2
@ TomášZato: Se você chamar delete em um ponteiro aleatório, estará ferrado. Nunca há uma boa razão para fazer isso. De fato, se você estiver chamando manualmente delete em qualquer lugar que não seja um destruidor de ponteiro inteligente, provavelmente desejará dar uma segunda olhada no motivo pelo qual não está usando um ponteiro inteligente ou algum outro gerenciador de objetos.
Eclipse
shared_array é apenas do impulso, sim?
Dronz
30

Quando você chama delete em um ponteiro alocado por new, o destruidor do objeto apontado será chamado.

A * p = new A;

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

fonte
22

É nomeado "destruidor", não "desconstrutor".

Dentro do destruidor de cada classe, você deve excluir todas as outras variáveis ​​de membro que foram alocadas com novas.

editar: para esclarecer:

Diga que você tem

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

Alocar uma instância de B e, em seguida, excluir é limpo, porque o que B aloca internamente também será excluído no destruidor.

Mas instâncias da classe C vazam memória, porque alocam uma instância de A que não libera (nesse caso, C nem sequer tem um destruidor).

Sebastian Mach
fonte
5

Se você tiver um ponteiro comum ( A*), o destruidor não será chamado (e a memória, por Aexemplo, também não será liberada), a menos que você o explique deleteexplicitamente B. Se você deseja destruição automática, veja indicadores inteligentes como auto_ptr.

dente afiado
fonte
4

Você deve excluir A você mesmo no destruidor de B.

corné
fonte
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 você faz:

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

O destruidor será chamado apenas se sua classe base tiver a palavra-chave virtual.

Então, se você não tivesse um destruidor virtual, apenas ~ B () seria chamado. Mas como você tem um destruidor virtual, primeiro ~ D () será chamado, depois ~ B ().

Nenhum membro de B ou D alocado no heap será desalocado, a menos que você os exclua explicitamente. E excluí-los também chamará seu destruidor.

Brian R. Bondy
fonte
1

Você tem algo como

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

Se você ligar delete b;, nada acontecerá com a e você terá um vazamento de memória. Tentar se lembrar delete b->a;não é uma boa solução, mas existem outras.

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

Este é um destruidor para B que excluirá a. (Se a é 0, essa exclusão não faz nada. Se a não é 0, mas não aponta para a memória de novo, você obtém corrupção de heap.)

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

Dessa forma, você não tem a como ponteiro, mas sim um auto_ptr <> (shared_ptr <> também serve, ou outros ponteiros inteligentes), e ele é excluído automaticamente quando b é.

Qualquer uma dessas maneiras funciona bem, e eu usei as duas.

David Thornley
fonte
1

Fiquei me perguntando por que o destruidor da minha classe não foi chamado. O motivo foi que eu esqueci de incluir a definição dessa classe (#include "class.h"). Eu só tive uma declaração como "classe A"; e o compilador ficou satisfeito com isso e deixe-me chamar "delete".

Harri Luoma
fonte
Aumentar o nível de aviso do compilador
Phil1970 16/07/19
0

Não. O ponteiro será excluído. Você deve chamar a exclusão em A explícita no destruidor de B.

RvdK
fonte
Estou fazendo isso, minha pergunta era: o destruidor é chamado?
22412 Nick Bolton
0

O destruidor para o objeto da classe A será chamado apenas se delete for chamado para esse objeto. Certifique-se de excluir esse ponteiro no destruidor da classe B.

Para obter mais informações sobre o que acontece quando a exclusão é chamada em um objeto, consulte: http://www.parashift.com/c++-faq-lite/freestore-mgmt.html#faq-16.9

Kris Kumler
fonte
0

não, ele não chamará destruidor para a classe A, você deve chamá-lo explicitamente (como PoweRoy disse), excluir a linha 'delete ptr;' no exemplo 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
fonte