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


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.