Нужно ли явно вызывать базовый виртуальный деструктор?


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
Пожалуйста, включите вывод программы (псевдо). это поможет читателю.
— Кулдип Сингх Дакка

@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 из Derived для «цепочки» вызова, компилятор генерирует код для вызова деструкторов в обратном порядке, в котором были вызваны их конструкторы.


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.


Хорошая иллюстрация. Если вы попытаетесь вызвать деструктор базового класса из производного класса, вы должны получить ошибку компилятора, похожую на «error: нет соответствующей функции для вызова 'BASE :: BASE ()' <newline> ~ BASE ();" По крайней мере, это поведение моего компилятора g ++ 7.x.
— Кемин Чжоу


1

Деструкторы в C ++ автоматически вызываются в порядке их конструирования (Derived затем Base) только при объявлении деструктора базового классаvirtual .

Если нет, то только деструктор базового класса вызывается во время удаления объекта.

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

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