Вопрос: Когда использовать виртуальные деструкторы?


У меня есть глубокое понимание большинства теорий ОО, но одна вещь, которая меня смущает, - это виртуальные деструкторы.

Я думал, что деструктор всегда получает вызов независимо от того, что и для каждого объекта в цепочке.

Когда вы собираетесь сделать их виртуальными и почему?


1181


источник


Ответы:


Виртуальные деструкторы полезны, когда вы можете удалить экземпляр производного класса с помощью указателя на базовый класс:

class Base 
{
    // some virtual methods
};

class Derived : public Base
{
    ~Derived()
    {
        // Do some important cleanup
    }
};

Здесь вы заметите, что я не объявлял деструктор базы virtual, Теперь давайте посмотрим следующий фрагмент:

Base *b = new Derived();
// use b
delete b; // Here's the problem!

Поскольку деструктор базы не является virtualа также bэто Base*указывая на Derivedобъект, delete bимеет неопределенное поведение :

delete b], если статический тип   объект, подлежащий удалению, отличается от его динамического типа, статический   тип должен быть базовым классом динамического типа объекта, который должен быть   удалены и статический тип должен иметь виртуальный деструктор или   поведение не определено ,

В большинстве реализаций вызов деструктора будет разрешен как любой не виртуальный код, а это означает, что деструктор базового класса будет вызываться, но не тот, который является производным классом, что приведет к утечке ресурсов.

Подводя итог, всегда создавайте деструкторы базовых классов virtualкогда они предназначены для манипулирования полиморфно.

Если вы хотите предотвратить удаление экземпляра с помощью указателя базового класса, вы можете сделать деструктор базового класса защищенным и не виртуальным; при этом компилятор не позволит вам позвонить deleteна указатель базового класса.

Вы можете узнать больше о деструкторе виртуальности и виртуального базового класса в эта статья от Херба Саттера ,


1285



Объявлять деструкторы виртуальными в полиморфных базовых классах. Это пункт 7 в книге Скотта Мейерса, Эффективный C ++ , Мейерс продолжает обобщать, что если класс Любые виртуальной функции, он должен иметь виртуальный деструктор, а классы, не предназначенные для базовых классов или не предназначенные для использования полиморфно, должны не объявлять виртуальные деструкторы.


159



Виртуальный конструктор невозможен, но возможен виртуальный деструктор. Давайте экспериментировать ....

#include <iostream>

using namespace std;

class Base
{
public:
    Base(){
        cout << "Base Constructor Called\n";
    }
    ~Base(){
        cout << "Base Destructor called\n";
    }
};

class Derived1: public Base
{
public:
    Derived1(){
        cout << "Derived constructor called\n";
    }
    ~Derived1(){
        cout << "Derived destructor called\n";
    }
};

int main()
{
    Base *b = new Derived1();
    delete b;
}

Вышеприведенный код выводит следующее:

Base Constructor Called
Derived constructor called
Base Destructor called

Построение производного объекта следует за правилом построения, но когда мы удаляем указатель «b» (базовый указатель), мы обнаружили, что только базовый деструктор является вызовом. Но этого не должно быть. Чтобы сделать правильную вещь, мы должны сделать базовый деструктор виртуальным. Теперь посмотрим, что произойдет в следующем:

#include <iostream>

using namespace std;

class Base
{ 
public:
    Base(){
        cout << "Base Constructor Called\n";
    }
    virtual ~Base(){
        cout << "Base Destructor called\n";
    }
};

class Derived1: public Base
{
public:
    Derived1(){
        cout << "Derived constructor called\n";
    }
    ~Derived1(){
        cout << "Derived destructor called\n";
    }
};

int main()
{
    Base *b = new Derived1();
    delete b;
}

Выход изменился следующим образом:

Base Constructor Called
Derived constructor called
Derived destructor called
Base Destructor called

Таким образом, уничтожение базового указателя (который принимает выделение на производном объекте!) Следует за правилом уничтожения, т.е. сначала выведенным потом базой. С другой стороны, для конструктора нет ничего похожего на виртуальный конструктор.


151



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

Как должно быть отменено удаление в C ++?

Я много лет использую C ++, и мне все равно удается повесить себя.


36



Делайте деструктор виртуальным, когда ваш класс является полиморфным.


29



Вызов деструктора с помощью указателя на базовый класс

struct Base {
  virtual void f() {}
  virtual ~Base() {}
};

struct Derived : Base {
  void f() override {}
  ~Derived() override {}
};

Base* base = new Derived;
base->f(); // calls Derived::f
base->~Base(); // calls Derived::~Derived

Вызов виртуального деструктора ничем не отличается от любого другого вызова виртуальной функции.

Для base->f(), вызов будет отправлен Derived::f(), и это то же самое для base->~Base()- его основная функция - Derived::~Derived()будет вызываться.

То же самое происходит, когда деструктор называется косвенно, например. delete base;, deleteзаявление вызовет base->~Base()который будет отправлен Derived::~Derived(),

Абстрактный класс с не виртуальным деструктором

Если вы не собираетесь удалять объект с помощью указателя на его базовый класс, тогда нет необходимости иметь виртуальный деструктор. Просто сделай это protectedтак что это не будет называться случайно:

// library.hpp

struct Base {
  virtual void f() = 0;

protected:
  ~Base() = default;
};

void CallsF(Base& base);
// CallsF is not going to own "base" (i.e. call "delete &base;").
// It will only call Base::f() so it doesn't need to access Base::~Base.

//-------------------
// application.cpp

struct Derived : Base {
  void f() override { ... }
};

int main() {
  Derived derived;
  CallsF(derived);
  // No need for virtual destructor here as well.
}

10



Мне нравится думать о интерфейсах и реализациях интерфейсов. В языке C ++ говорят, что это чистый виртуальный класс. Деструктор является частью интерфейса и ожидается, что он будет реализован. Поэтому деструктор должен быть чистым виртуальным. Как насчет конструктора? Конструктор фактически не является частью интерфейса, потому что объект всегда создается явно.


7



To be simple, Virtual destructor is to destruct the resources in a proper order, when you delete a base class pointer pointing to derived class object.

 #include<iostream>
 using namespace std;
 class B{
    public:
       B(){
          cout<<"B()\n";
       }
       virtual ~B(){ 
          cout<<"~B()\n";
       }
 };
 class D: public B{
    public:
       D(){
          cout<<"D()\n";
       }
       ~D(){
          cout<<"~D()\n";
       }
 };
 int main(){
    B *b = new D();
    delete b;
    return 0;
 }

OUTPUT:
B()
D()
~D()
~B()

==============
If you don't give ~B()  as virtual. then output would be 
B()
D()
~B()
where destruction of ~D() is not done which leads to leak


6



Virtual keyword for destructor is necessary when you want different destructors should follow proper order while objects is being deleted through base class pointer. for example:

Base *myObj = new Derived();
// Some code which is using myObj object
myObj->fun();
//Now delete the object
delete myObj ; 

If your derived class destructor is virtual then objects will be destrcuted in a order(firstly derived object then base ). If your derived class destructor is NOT virtual then only base class object will get deleted(because pointer is of base class "Base *myObj"). So there will be memory leak for derived object.


5



Virtual base class destructors are "best practice" - you should always use them to avoid (hard to detect) memory leaks. Using them, you can be sure all destructors in the inheritance chain of your classes are beeing called (in proper order). Inheriting from a base class using virtual destructor makes the destructor of the inheriting class automatically virtual, too (so you do not have to retype 'virtual' in the inheriting class destructor declaration).


2