Czy muszę jawnie wywoływać podstawowy wirtualny destruktor?

350

Podczas nadpisywania klasy w C ++ (za pomocą wirtualnego destruktora) implementuję ponownie destruktor jako wirtualny w klasie dziedziczącej, ale czy muszę wywoływać podstawowy destruktor?

Jeśli tak, to wyobrażam sobie, że to coś takiego ...

MyChildClass::~MyChildClass() // virtual in header
{
    // Call to base destructor...
    this->MyBaseClass::~MyBaseClass();

    // Some destructing specific to MyChildClass
}

Czy mam rację?

Nick Bolton
źródło

Odpowiedzi:

469

Nie, destruktory są wywoływane automatycznie w odwrotnej kolejności budowy. (Ostatnie klasy podstawowe). Nie nazywaj niszczycieli klasy bazowej.

Lou Franco
źródło
Co z czystymi wirtualnymi niszczycielami? Mój linker próbuje to nazwać na końcu nie-wirtualnego destruktora mojej odziedziczonej klasy;
cjcurrie
40
nie możesz mieć czystego wirtualnego destruktora bez ciała. Po prostu daj mu puste ciało. W zwykłej czysto wirtualnej metodzie zamiast tego wywoływana jest funkcja zastępująca, w przypadku destruktorów wszystkie są wywoływane, więc musisz podać ciało. = 0 oznacza po prostu, że musi zostać zastąpione, więc nadal jest użyteczną konstrukcją, jeśli jej potrzebujesz.
Lou Franco
1
To pytanie może być powiązane i pomóc w pytaniach / 15265106 / ca-missing-vtable-error .
Paul-Sebastian Manole,
Dlaczego kod Nicka Boltona nie powoduje błędu segmentacji, chociaż dwukrotnie wywołuje podstawowy niszczyciel, a dwukrotne wywołanie deletewskaźnika do klasy podstawowej powoduje błąd segmentacji?
Maggyero,
2
Nie masz gwarancji błędu segmentacji z żadnym błędnym kodem. Ponadto wywołanie destruktora nie zwalnia pamięci.
Lou Franco
92

Nie, nie musisz wywoływać podstawowego destruktora, podstawowy destruktor jest zawsze wzywany przez pochodną destruktor. Proszę zobaczyć moją pokrewną odpowiedź tutaj dotyczącą kolejności zniszczenia .

Aby zrozumieć, dlaczego chcesz wirtualnego destruktora w klasie podstawowej, zapoznaj się z poniższym kodem:

class B
{
public:
    virtual ~B()
    {
        cout<<"B destructor"<<endl;
    }
};


class D : public B
{
public:
    virtual ~D()
    {
        cout<<"D destructor"<<endl;
    }
};

Kiedy to zrobisz:

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

Następnie, jeśli nie masz wirtualnego destruktora w B, wywoływana będzie tylko ~ B (). Ale ponieważ masz wirtualny destruktor, najpierw zostanie wywołane ~ D (), a następnie ~ B ().

Brian R. Bondy
źródło
20
Dołącz wyjście programu (pseudo). pomoże czytelnikowi.
Kuldeep Singh Dhaka
@KuldeepSinghDhaka Czytelnik może to zobaczyć na żywo na wandbox.org/permlink/KQtbZG1hjVgceSlO .
świnie
27

Co powiedzieli inni, ale zauważ również, że nie musisz deklarować destruktora jako wirtualnego w klasie pochodnej. Po zadeklarowaniu wirtualnego destruktora, tak jak w klasie bazowej, wszystkie pochodne destruktory będą wirtualne, niezależnie od tego, czy je zadeklarujesz, czy nie. Innymi słowy:

struct A {
   virtual ~A() {}
};

struct B : public A {
   virtual ~B() {}   // this is virtual
};

struct C : public A {
   ~C() {}          // this is virtual too
};
Wodzu
źródło
1
co jeśli ~ B nie zostanie zadeklarowane jako wirtualne? Czy ~ C jest nadal wirtualny?
Czy
5
Tak. Gdy metoda wirtualna (dowolna, nie tylko destruktor) zostanie uznana za wirtualną, wszystkie przesłonięcia tej metody w klasach pochodnych są automatycznie wirtualne. W takim przypadku, nawet jeśli nie zadeklarujesz ~ B jako wirtualnego, nadal tak jest, podobnie jak ~ C.
boycy
1
Ale w przeciwieństwie do innych przesłoniętych metod o tej samej nazwie i parametrach odpowiadających im metod w klasie bazowej, nazwa destruktora jest inna. Czy to ma znaczenie? @boycy
Yuan Wen
1
@YuanWen nie, nie będzie, pochodny (jeden i jedyny) destruktor zawsze nadpisuje (jeden i jedyny) niszczyciel swojej klasy podstawowej.
boycy
10

Nie. W przeciwieństwie do innych metod wirtualnych, w których jawnie wywołujesz metodę Base z pochodnej w celu „połączenia” wywołania, kompilator generuje kod do wywoływania destruktorów w odwrotnej kolejności, w jakiej zostały wywołane ich konstruktory.

itsmatt
źródło
9

Nie, nigdy nie nazywasz niszczyciela klasy bazowej, zawsze jest wywoływany automatycznie, jak zauważyli inni, ale oto dowód koncepcji z wynikami:

class base {
public:
    base()  { cout << __FUNCTION__ << endl; }
    ~base() { cout << __FUNCTION__ << endl; }
};

class derived : public base {
public:
    derived() { cout << __FUNCTION__ << endl; }
    ~derived() { cout << __FUNCTION__ << endl; } // adding call to base::~base() here results in double call to base destructor
};


int main()
{
    cout << "case 1, declared as local variable on stack" << endl << endl;
    {
        derived d1;
    }

    cout << endl << endl;

    cout << "case 2, created using new, assigned to derive class" << endl << endl;
    derived * d2 = new derived;
    delete d2;

    cout << endl << endl;

    cout << "case 3, created with new, assigned to base class" << endl << endl;
    base * d3 = new derived;
    delete d3;

    cout << endl;

    return 0;
}

Dane wyjściowe to:

case 1, declared as local variable on stack

base::base
derived::derived
derived::~derived
base::~base


case 2, created using new, assigned to derive class

base::base
derived::derived
derived::~derived
base::~base


case 3, created with new, assigned to base class

base::base
derived::derived
base::~base

Press any key to continue . . .

Jeśli ustawisz destruktor klasy bazowej jako wirtualny, który należy, wyniki przypadku 3 będą takie same jak przypadku 1 i 2.

zar
źródło
Dobra ilustracja Jeśli spróbujesz wywołać destruktor klasy bazowej z klasy pochodnej, powinieneś otrzymać błąd kompilatora podobny do „error: brak pasującej funkcji dla wywołania„ BASE :: BASE () ”<newline> ~ BASE ();” Przynajmniej takie jest zachowanie mojego kompilatora g ++ 7.x.
Kemin Zhou,
6

Nie. To jest automatycznie wywoływane.

Benoit
źródło
1

Destruktory w C ++ są automatycznie wywoływane w kolejności ich konstrukcji (pochodne, a następnie podstawowe) tylko wtedy, gdy zadeklarowany jest destruktor klasy podstawowejvirtual .

Jeśli nie, to w momencie usuwania obiektu wywoływany jest tylko destruktor klasy bazowej.

Przykład: Bez wirtualnego niszczyciela

#include <iostream>

using namespace std;

class Base{
public:
  Base(){
    cout << "Base Constructor \n";
  }

  ~Base(){
    cout << "Base Destructor \n";
  }

};

class Derived: public Base{
public:
  int *n;
  Derived(){
    cout << "Derived Constructor \n";
    n = new int(10);
  }

  void display(){
    cout<< "Value: "<< *n << endl;
  }

  ~Derived(){
    cout << "Derived Destructor \n";
  }
};

int main() {

 Base *obj = new Derived();  //Derived object with base pointer
 delete(obj);   //Deleting object
 return 0;

}

Wynik

Base Constructor
Derived Constructor
Base Destructor

Przykład: Z wirtualnym niszczycielem bazy

#include <iostream>

using namespace std;

class Base{
public:
  Base(){
    cout << "Base Constructor \n";
  }

  //virtual destructor
  virtual ~Base(){
    cout << "Base Destructor \n";
  }

};

class Derived: public Base{
public:
  int *n;
  Derived(){
    cout << "Derived Constructor \n";
    n = new int(10);
  }

  void display(){
    cout<< "Value: "<< *n << endl;
  }

  ~Derived(){
    cout << "Derived Destructor \n";
    delete(n);  //deleting the memory used by pointer
  }
};

int main() {

 Base *obj = new Derived();  //Derived object with base pointer
 delete(obj);   //Deleting object
 return 0;

}

Wynik

Base Constructor
Derived Constructor
Derived Destructor
Base Destructor

Zaleca się zadeklarowanie destruktora klasy bazowej jako virtual przeciwnym razie powoduje on niezdefiniowane zachowanie.

Odniesienie: Virtual Destructor

Adarsh ​​Kumar
źródło