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ę?

Odpowiedzi:


469

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


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


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

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.


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.


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,


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

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.