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


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.