Чи потрібно явно викликати базовий віртуальний деструктор?


351

При переосмисленні класу на C ++ (з віртуальним деструктором) я знову реалізую деструктор як віртуальний у класі спадкування, але чи потрібно мені викликати базовий деструктор?

Якщо так, я думаю, що це щось подібне ...

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

    // Some destructing specific to MyChildClass
}

Чи правий я?

Відповіді:


472

Ні, деструктори викликаються автоматично в зворотному порядку побудови. (Останні базові класи). Не викликайте деструкторів базового класу.


А як з чистими віртуальними деструкторами? Мій лінкер намагається викликати це наприкінці невіртуального деструктора мого спадкового класу;
— cjcurrie

40
ви не можете мати чистого віртуального деструктора без тіла. Просто дайте йому порожнє тіло. За допомогою звичайного чистого віртуального методу викликується переважаюча функція, а деструктори - всі вони викликаються, тому вам потрібно забезпечити тіло. Значення = 0 означає, що його треба переосмислити, тому все-таки корисна конструкція, якщо вона потрібна.
— Лу Франко

1
Це питання може бути пов’язане та допоможе питаннями / 15265106 / ca-missing-vtable-error .
— Пол-Себастьян Маноле

Чому код Ніка Болтона не спричиняє помилки сегментації, хоча він викликає базовий деструктор двічі, а дзвінок deleteна вказівник на базовий клас двічі викликає помилку сегментації?
— Маджієро

2
Вам не гарантується помилка сегментації з будь-яким неправильним кодом. Також виклик деструктора не звільняє пам'ять.
— Лу Франко

92

Ні, вам не потрібно викликати базовий деструктор, базовий деструктор завжди викликає вас похідним деструктором. Будь ласка, дивіться мою відповідь тут щодо порядку знищення .

Щоб зрозуміти, чому вам потрібен віртуальний деструктор у базовому класі, перегляньте код нижче:

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


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

Коли ви робите:

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

Тоді, якщо у вас не було віртуального деструктора в B, викликувся б лише ~ B (). Але оскільки у вас є віртуальний деструктор, спочатку буде викликано ~ D (), потім ~ B ().


20
Будь ласка, включіть програму (псевдо). це допоможе читачеві.
— Kuldeep Singh Dhaka

@KuldeepSinghDhaka Читач може побачити це в прямому ефірі на wandbox.org/permlink/KQtbZG1hjVgceSlO .
— свиня

27

Що сказали інші, але також зауважте, що вам не потрібно оголошувати деструктор віртуальним у похідному класі. Після того, як ви оголосите деструктора віртуальним, як це робите в базовому класі, всі похідні деструктори будуть віртуальними, незалежно від того, декларуєте ви їх так чи ні. Іншими словами:

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

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

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

1
що робити, якщо ~ B не оголошено віртуальним? ~ C все ще віртуально?
— Буде

5
Так. Коли віртуальний метод (будь-який, а не лише деструктор) оголошується віртуальним, всі зміни цього методу у похідних класах автоматично є віртуальними. У цьому випадку, навіть якщо ви не оголосите ~ B віртуальним, він все одно є, і так це ~ C.
— хлопчик

1
Але на відміну від інших перекритих методів, що мають те саме ім'я та параметри відповідних методів у базовому класі, ім’я деструктора відрізняється. Чи це буде важливо? @boycy
— Юань Вень

1
@YuanWen ні, це не буде, (один і єдиний) похідний деструктор завжди переосмислює базовий клас (один і єдиний) деструктор.
— хлопець

10

Ні. На відміну від інших віртуальних методів, де ви явно закликаєте метод Base з "Похідного" для "ланцюга" виклику, компілятор генерує код для виклику деструкторів у зворотному порядку, в якому викликали їх конструктори.


9

Ні, ви ніколи не називаєте деструктор базового класу, він завжди викликається автоматично, як вказали інші, але ось доказ концепції з результатами:

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

Вихід:

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

Якщо ви встановите деструктор базового класу як віртуальний, який слід, то випадок 3 результатів буде таким же, як і випадки 1 та 2.


Гарна ілюстрація. Якщо ви спробуєте викликати деструктор базового класу з похідного класу, ви повинні отримати помилку компілятора, аналогічну "помилка: немає функції узгодження для виклику до" BASE :: BASE () '<newline> ~ BASE (); " Принаймні, така поведінка мого компілятора g ++ 7.x.
— Kemin Zhou


1

Деструктори в C ++ автоматично викликаються в порядку їх побудови (Похідне тоді База) лише тоді, коли оголошено деструктор класу Basevirtual .

Якщо ні, то під час видалення об'єкта викликається лише деструктор базового класу.

Приклад: Без віртуального деструктора

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

}

Вихідні дані

Base Constructor
Derived Constructor
Base Destructor

Приклад: З базовим віртуальним деструктором

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

}

Вихідні дані

Base Constructor
Derived Constructor
Derived Destructor
Base Destructor

Рекомендується деструктор базового класу оголосити як virtualінакше, це спричиняє не визначену поведінку.

Довідка: Віртуальний деструктор

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.