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

Я хорошо разбираюсь в большинстве OOP теорий, но единственное, что меня сильно смущает, - это виртуальные деструкторы.

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

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


person Lodle    schedule 20.01.2009    source источник
comment
См. Это: Виртуальный деструктор   -  person Naveen    schedule 20.01.2009
comment
Каждый деструктор down вызывается несмотря ни на что. virtual следит за тем, чтобы он начинался сверху, а не посередине.   -  person Mooing Duck    schedule 29.06.2013
comment
связанный вопрос: Когда не следует использовать виртуальные деструкторы?   -  person Eitan T    schedule 04.08.2013
comment
@MooingDuck, это несколько вводящий в заблуждение комментарий.   -  person Euri Pinhollow    schedule 30.07.2017
comment
@EuriPinhollow Mind разрабатывает?   -  person Franklin Yu    schedule 08.11.2017
comment
@FranklinYu, хорошо, что вы спросили, потому что теперь я не вижу никаких проблем с этим комментарием (кроме попытки дать ответ в комментариях).   -  person Euri Pinhollow    schedule 08.11.2017
comment
@FranklinYu Я, вероятно, думал о том, что должно быть более конкретное утверждение (т.е. когда разрушение может начинаться в середине дерева наследования, а не сверху), но это то, что ответы уже подробно описаны.   -  person Euri Pinhollow    schedule 08.11.2017
comment
Меня также смущает ответ @MooingDuck. Разве это не должно быть вверх вместо down, если вы используете понятие подкласса (ниже) и суперкласса (выше)?   -  person Nibor    schedule 20.06.2019
comment
@Nibor: Да, если вы используете это понятие. Примерно половина людей, с которыми я разговариваю, рассматривают суперклассы, как указано выше, а половина - о суперклассах, как показано ниже, так что оба являются противоречивыми стандартами, что сбивает с толку. Я думаю, что суперкласс, как указано выше, немного более распространен, но меня этому не учили :(   -  person Mooing Duck    schedule 20.06.2019
comment
эта статья может помочь. medium.com / @ tunvirrahmantusher /   -  person Tunvir Rahman Tusher    schedule 26.12.2019


Ответы (17)


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

class Base 
{
    // some virtual methods
};

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

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

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

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

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

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

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

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

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

person Luc Touraille    schedule 20.01.2009
comment
Это могло бы объяснить, почему у меня были массовые утечки на фабрике, которую я сделал раньше. Теперь все имеет смысл. Спасибо - person Lodle; 20.01.2009
comment
Что ж, это плохой пример, поскольку нет членов данных. Что, если Base и Derived имеют все переменные автоматического хранения? т.е. в деструкторе нет специального или дополнительного пользовательского кода. Можно ли вообще перестать писать деструкторы? Или у производного класса все еще будет утечка памяти? - person bobobobo; 08.07.2012
comment
@ workmad3 не виртуальный для этого не нужен, не так ли? Может быть, бесполезно, но не обязательно отсутствовать. - person Steven Kramer; 15.03.2014
comment
когда есть только простые типы данных (например, без указателей), ожидаем ли мы по-прежнему утечки памяти, если не будем использовать виртуальные деструкторы? - person ransh; 15.02.2015
comment
@ransh: как @bobobobo уже сказал в предыдущем комментарии, даже если классы не имеют переменных-членов, вызов delete для Base*, который указывает на объект Derived, является неопределенным поведением. Это означает, что программа может делать что угодно, включая утечку памяти. - person Luc Touraille; 16.02.2015
comment
Из статьи Херба Саттера: Рекомендация №4: деструктор базового класса должен быть либо общедоступным и виртуальным, либо защищенным и невиртуальным. - person Sundae; 09.02.2016
comment
Также из статьи - «если вы удалите полиморфно без виртуального деструктора, вы вызовете ужасный призрак неопределенного поведения, призрак, которого я лично не хотел бы встречать даже в умеренно хорошо освещенном переулке, большое вам спасибо». ржу не могу - person Bondolin; 29.02.2016
comment
@Sundae Пожалуйста, объясните protected and non-virtual part. - person ajaysinghnegi; 26.07.2019
comment
@Jos: Из статьи: Короче говоря, у вас остается одна из двух ситуаций. Либо: а) вы хотите разрешить полиморфное удаление с помощью базового указателя, и в этом случае деструктор должен быть виртуальным и общедоступным; или б) вы этого не сделаете, и в этом случае деструктор должен быть невиртуальным и защищенным, последнее - для предотвращения нежелательного использования. - person Sundae; 05.08.2019
comment
Чтобы прояснить, похоже, что виртуальный деструктор необходим, если тип будет передан delete, когда он был выделен как производный тип. Производному типу не нужен виртуальный деструктор, если к нему не применимо то же самое. Таким образом, класс final никогда не нуждается в виртуальном деструкторе, даже если он является производным от класса, у которого он есть. - person fuzzyTew; 22.03.2020
comment
@fuzzyTew Если класс D является производным от класса B с виртуальным деструктором, то D также имеет виртуальный деструктор, даже если он не объявляет его явно. В большинстве случаев виртуальный деструктор необходимо объявлять только классу на вершине иерархии, а не остальной части иерархии. - person Luc Touraille; 24.03.2020
comment
@Luc Touraille: да, ваш вывод решает мою проблему. Всегда можно создать экземпляр любого неабстрактного класса. Поэтому, чтобы предотвратить это «неопределенное поведение», любой неабстрактный класс всегда должен определяться с помощью виртуального деструктора. Но большинство классов, с которыми я работал, вообще не имеют виртуального деструктора. Так что, возможно, у одного из их базовых классов он уже есть! - person Steven Lee; 21.01.2021

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

#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

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

person Tunvir Rahman Tusher    schedule 09.04.2013
comment
виртуальный конструктор невозможен, это означает, что вам не нужно писать виртуальный конструктор самостоятельно. Построение производного объекта должно следовать цепочке построения от производного к основному. Таким образом, вам не нужно писать виртуальное ключевое слово для вашего конструктора. Спасибо - person Tunvir Rahman Tusher; 19.04.2013
comment
@Murkantilism, виртуальные конструкторы сделать нельзя, это правда. Конструктор нельзя пометить как виртуальный. - person cmeub; 22.04.2013
comment
@cmeub, но есть идиома, чтобы добиться от виртуального конструктора того, что вы хотели бы. См. parashift.com/c++-faq-lite/virtual-ctors.html - person cape1232; 03.10.2013
comment
@TunvirRahmanTusher не могли бы вы объяснить, почему называется Base Destructor ?? - person rimalroshan; 11.11.2017
comment
@rimiro Это автоматически с ++. Вы можете перейти по ссылке stackoverflow.com/questions/677620/ - person Tunvir Rahman Tusher; 11.11.2017

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

person Bill the Lizard    schedule 20.01.2009
comment
+ Если у класса есть какая-либо виртуальная функция, он должен иметь виртуальный деструктор, и эти классы, не предназначенные для использования в качестве базовых классов или не предназначенные для использования полиморфно, не должны объявлять виртуальные деструкторы: есть ли случаи, когда имеет смысл нарушать это правило? Если нет, имеет ли смысл заставить компилятор проверить это условие и выдать ошибку, если оно не выполнено? - person Giorgio; 06.05.2012
comment
@Giorgio Я не знаю никаких исключений из правил. Но я бы не стал оценивать себя как эксперта по C ++, поэтому вы можете опубликовать это как отдельный вопрос. Для меня имеет смысл предупреждение компилятора (или предупреждение от инструмента статического анализа). - person Bill the Lizard; 06.05.2012
comment
Классы могут быть спроектированы так, чтобы их нельзя было удалить с помощью указателя определенного типа, но при этом они по-прежнему имеют виртуальные функции - типичным примером является интерфейс обратного вызова. Его реализация не удаляется с помощью указателя интерфейса обратного вызова, поскольку он используется только для подписки, но он имеет виртуальные функции. - person dascandy; 15.01.2016
comment
@dascandy Exactly - это или все многие другие ситуации, когда мы используем полиморфное поведение, но не выполняем управление хранилищем с помощью указателей - например, поддержание объектов с автоматической или статической продолжительностью, с указателями, используемыми только в качестве маршрутов наблюдения. Нет необходимости / цели в реализации виртуального деструктора в любых таких случаях. Поскольку мы просто цитируем здесь людей, я предпочитаю Саттера сверху: Рекомендация №4: деструктор базового класса должен быть либо общедоступным и виртуальным, либо защищенным и невиртуальным. Последнее гарантирует, что любой, кто случайно пытается удалить с помощью базового указателя, показывает ошибку своего пути. - person underscore_d; 23.04.2016
comment
@Giorgio На самом деле есть уловка, которую можно использовать и избежать виртуального вызова деструктора: привязать через константную ссылку производный объект к базе, например const Base& = make_Derived();. В этом случае будет вызван деструктор Derived prvalue, даже если он не виртуальный, поэтому можно сэкономить накладные расходы, вносимые vtables / vpointers. Конечно, возможности довольно ограничены. Андрей Александреску упомянул об этом в своей книге Современный дизайн C ++. - person vsoftco; 02.11.2016
comment
@Giorgio есть предупреждение в gcc только для этой ситуации. dascandy, они изначально не учли вашу точку зрения как задокументированную здесь. - person davidvandebunte; 25.11.2017
comment
Почему все работает, если нет виртуального метода без полиморфного деструктора? РЕДАКТИРОВАТЬ ответ: они не делают, но в этом случае вам никогда не нужно будет писать Base *b = new Derived1, который является источником всех проблем, а скорее Derived1 *b = new Derived1. Кроме того, без new не может быть проблем, поскольку компилятор всегда знает реальный тип того, что выходит за рамки. - person Ciro Santilli 新疆再教育营六四事件ۍ 14.02.2018

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

Как должно вести себя переопределение удаления в C ++?

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

person BigSandwich    schedule 21.01.2009
comment
Я посмотрел на ваш вопрос и увидел, что вы объявили базовый деструктор виртуальным. Значит, удаление указателя базового класса при отсутствии виртуального деструктора приведет к тому, что неопределенное поведение останется действительным в отношении этого вашего вопроса? Поскольку в этом вопросе, когда вы вызываете delete, производный класс (созданный его оператором new) сначала проверяется на совместимую версию. Поскольку он нашел там одного, его назвали. Итак, не думаете ли вы, что было бы лучше сказать, что удаление указателя базового класса при отсутствии деструктора приведет к неопределенному поведению? - person ubuntugod; 23.02.2016
comment
Это почти то же самое. Конструктор по умолчанию не виртуальный. - person BigSandwich; 27.02.2016

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

person yesraaj    schedule 20.01.2009

Вызов деструктора через указатель на базовый класс

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.
}
person Abyx    schedule 18.05.2015
comment
Нужно ли явно объявлять ~Derived() во всех производных классах, даже если это просто ~Derived() = default? Или это подразумевается языком (что позволяет безопасно опустить)? - person Ponkadoodle; 18.08.2016
comment
@Wallacoloo нет, объявляйте это только тогда, когда это необходимо. Например. поместить в раздел protected или сделать его виртуальным с помощью override. - person Abyx; 18.08.2016

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

 #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

person Prakash GiBBs    schedule 26.08.2016
comment
Отсутствие базового виртуального деструктора и вызов delete по базовому указателю приводит к неопределенному поведению. - person James Adkison; 13.10.2016
comment
@JamesAdkison, почему это приводит к неопределенному поведению ?? - person rimalroshan; 11.11.2017
comment
@rimiro < / а>. У меня нет копии, но ссылка приведет вас к комментарию, где кто-то ссылается на местоположение в стандарте. - person James Adkison; 11.11.2017
comment
@rimiro Если удаление, следовательно, может быть выполнено полиморфно через интерфейс базового класса, тогда оно должно вести себя виртуально и должно быть виртуальным. Действительно, язык требует этого - если вы удаляете полиморфно без виртуального деструктора, вы вызываете ужасный призрак неопределенного поведения, призрак, которого я лично не хотел бы встречать даже в умеренно хорошо освещенном переулке, большое вам спасибо. (gotw.ca/publications/mill18.htm) - Херб Саттер - person James Adkison; 11.11.2017

Ключевое слово Virtual для деструктора необходимо, когда вы хотите, чтобы разные деструкторы следовали правильному порядку, пока объекты удаляются с помощью указателя базового класса. Например:

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

Если деструктор базового класса виртуальный, объекты будут уничтожены в определенном порядке (сначала производный объект, затем базовый). Если ваш деструктор базового класса НЕ виртуальный, то будет удален только объект базового класса (потому что указатель имеет базовый класс «Base * myObj»). Таким образом, будет утечка памяти для производного объекта.

person Mukul Kashmira    schedule 29.01.2015

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

person Dragan Ostojic    schedule 08.11.2012
comment
Это другой взгляд на тот же вопрос. Если мы будем думать в терминах интерфейсов, а не базового класса и производного класса, то естественный вывод: если он является частью интерфейса, то сделайте его виртуальным. Если это не так. - person Dragan Ostojic; 09.11.2012
comment
+1 за указание схожести объектно-ориентированной концепции интерфейса и чистого виртуального класса C ++. Что касается деструктора, который должен быть реализован: в этом часто нет необходимости. Если класс не управляет ресурсом, таким как необработанная динамически выделяемая память (например, не через интеллектуальный указатель), дескриптор файла или дескриптор базы данных, использование деструктора по умолчанию, созданного компилятором, нормально в производных классах. И обратите внимание, что если деструктор (или любая функция) объявлен virtual в базовом классе, он автоматически virtual в производном классе, даже если он не объявлен так. - person DavidRR; 11.07.2013
comment
При этом упускается важная деталь: деструктор не обязательно является частью интерфейса. Можно легко запрограммировать классы, которые имеют полиморфные функции, но которые вызывающий не управляет / не может удалить. Тогда у виртуального деструктора нет цели. Конечно, чтобы гарантировать это, невиртуальный деструктор - вероятно, по умолчанию - должен быть закрытым. Если бы мне пришлось угадывать, я бы сказал, что такие классы чаще используются внутри проектов, но это не делает их менее актуальными в качестве примера / нюанса во всем этом. - person underscore_d; 23.04.2016

Что такое виртуальный деструктор или как использовать виртуальный деструктор

Деструктор класса - это функция с тем же именем, что и у класса, предшествующего ~, которая перераспределяет память, выделенную классом. Зачем нужен виртуальный деструктор

См. Следующий пример с некоторыми виртуальными функциями

В образце также рассказывается, как преобразовать букву в верхнюю или нижнюю.

#include "stdafx.h"
#include<iostream>
using namespace std;
// program to convert the lower to upper orlower
class convertch
{
public:
  //void convertch(){};
  virtual char* convertChar() = 0;
  ~convertch(){};
};

class MakeLower :public convertch
{
public:
  MakeLower(char *passLetter)
  {
    tolower = true;
    Letter = new char[30];
    strcpy(Letter, passLetter);
  }

  virtual ~MakeLower()
  {
    cout<< "called ~MakeLower()"<<"\n";
    delete[] Letter;
  }

  char* convertChar()
  {
    size_t len = strlen(Letter);
    for(int i= 0;i<len;i++)
      Letter[i] = Letter[i] + 32;
    return Letter;
  }

private:
  char *Letter;
  bool tolower;
};

class MakeUpper : public convertch
{
public:
  MakeUpper(char *passLetter)
  {
    Letter = new char[30];
    toupper = true;
    strcpy(Letter, passLetter);
  }

  char* convertChar()
  {   
    size_t len = strlen(Letter);
    for(int i= 0;i<len;i++)
      Letter[i] = Letter[i] - 32;
    return Letter;
  }

  virtual ~MakeUpper()
  {
    cout<< "called ~MakeUpper()"<<"\n";
    delete Letter;
  }

private:
  char *Letter;
  bool toupper;
};


int _tmain(int argc, _TCHAR* argv[])
{
  convertch *makeupper = new MakeUpper("hai"); 
  cout<< "Eneterd : hai = " <<makeupper->convertChar()<<" ";     
  delete makeupper;
  convertch *makelower = new MakeLower("HAI");;
  cout<<"Eneterd : HAI = " <<makelower->convertChar()<<" "; 
  delete makelower;
  return 0;
}

Из приведенного выше примера видно, что деструктор для классов MakeUpper и MakeLower не вызывается.

См. Следующий пример с виртуальным деструктором

#include "stdafx.h"
#include<iostream>

using namespace std;
// program to convert the lower to upper orlower
class convertch
{
public:
//void convertch(){};
virtual char* convertChar() = 0;
virtual ~convertch(){}; // defined the virtual destructor

};
class MakeLower :public convertch
{
public:
MakeLower(char *passLetter)
{
tolower = true;
Letter = new char[30];
strcpy(Letter, passLetter);
}
virtual ~MakeLower()
{
cout<< "called ~MakeLower()"<<"\n";
      delete[] Letter;
}
char* convertChar()
{
size_t len = strlen(Letter);
for(int i= 0;i<len;i++)
{
Letter[i] = Letter[i] + 32;

}

return Letter;
}

private:
char *Letter;
bool tolower;

};
class MakeUpper : public convertch
{
public:
MakeUpper(char *passLetter)
{
Letter = new char[30];
toupper = true;
strcpy(Letter, passLetter);
}
char* convertChar()
{

size_t len = strlen(Letter);
for(int i= 0;i<len;i++)
{
Letter[i] = Letter[i] - 32;
}
return Letter;
}
virtual ~MakeUpper()
{
      cout<< "called ~MakeUpper()"<<"\n";
delete Letter;
}
private:
char *Letter;
bool toupper;
};


int _tmain(int argc, _TCHAR* argv[])
{

convertch *makeupper = new MakeUpper("hai");

cout<< "Eneterd : hai = " <<makeupper->convertChar()<<" \n";

delete makeupper;
convertch *makelower = new MakeLower("HAI");;
cout<<"Eneterd : HAI = " <<makelower->convertChar()<<"\n ";


delete makelower;
return 0;
}

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

Или перейдите по ссылке

https://web.archive.org/web/20130822173509/http://www.programminggallery.com/article_details.php?article_id=138

person user2578542    schedule 17.07.2013

Деструкторы виртуального базового класса - это «лучшая практика» - вы всегда должны использовать их, чтобы избежать (трудно обнаруживаемых) утечек памяти. Используя их, вы можете быть уверены, что все деструкторы в цепочке наследования ваших классов вызываются (в правильном порядке). При наследовании от базового класса с помощью виртуального деструктора деструктор наследующего класса также автоматически становится виртуальным (поэтому вам не нужно повторно вводить «виртуальный» в объявлении деструктора наследующего класса).

person Trantor    schedule 24.01.2017

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

class A
{
public:
    A() {}
    virtual void foo()
    {
        cout << "This is A." << endl;
    }
};

class B : public A
{
public:
    B() {}
    void foo()
    {
        cout << "This is B." << endl;
    }
};

int main(int argc, char* argv[])
{
    A *a = new B();
    a->foo();
    if(a != NULL)
    delete a;
    return 0;
}

Распечатаю:

This is B.

Без virtual он распечатает:

This is A.

И теперь вы должны понимать, когда использовать виртуальные деструкторы.

person gonjay    schedule 18.05.2014
comment
Нет, это только воспроизводит самые основы виртуальных функций, полностью игнорируя нюансы того, когда и почему деструктор должен быть одним - что не так интуитивно понятно, поэтому OP задал вопрос. (Кроме того, почему здесь ненужное динамическое размещение? Просто сделайте B b{}; A& a{b}; a.foo();. Проверка на NULL - который должен быть nullptr - перед deleteing - с неправильным отступом - не требуется: delete nullptr; определен как бездействующий. Во всяком случае, вы должны были проверить это перед вызовом ->foo(), иначе может возникнуть неопределенное поведение, если new каким-то образом не сработает.) - person underscore_d; 23.04.2016
comment
Вызвать delete по указателю NULL безопасно (т. Е. Вам не нужна защита if (a != NULL)). - person James Adkison; 13.10.2016
comment
@SaileshD Да, я знаю. Об этом я сказал в моем комментарии - person James Adkison; 18.04.2018

Если вы используете shared_ptr (только shared_ptr, а не unique_ptr), вам не обязательно иметь виртуальный деструктор базового класса:

#include <iostream>
#include <memory>

using namespace std;

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

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

int main()
{
    shared_ptr<Base> b(new Derived());
}

выход:

Base Constructor Called
Derived constructor called
Derived destructor called
Base Destructor called
person Zhenxiao Hao    schedule 06.03.2019
comment
Хотя это возможно, я бы отговорил кого-либо от его использования. Накладные расходы виртуального деструктора незначительны, и это просто дает возможность ошибиться, особенно для менее опытного программиста, который этого не знает. Это маленькое ключевое слово virtual может спасти вас от многих мучений. - person Michal Štein; 21.03.2020

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

person user2641018    schedule 09.09.2014

Я подумал, что было бы полезно обсудить поведение «undefined» или, по крайней мере, поведение undefined «сбой», которое может произойти при удалении через базовый класс (/ struct) без виртуального деструктора или, точнее, без vtable. В приведенном ниже коде перечислено несколько простых структур (то же самое верно и для классов).

#include <iostream>
using namespace std;

struct a
{
    ~a() {}

    unsigned long long i;
};

struct b : a
{
    ~b() {}

    unsigned long long j;
};

struct c : b
{
    ~c() {}

    virtual void m3() {}

    unsigned long long k;
};

struct d : c
{
    ~d() {}

    virtual void m4() {}

    unsigned long long l;
};

int main()
{
    cout << "sizeof(a): " << sizeof(a) << endl;
    cout << "sizeof(b): " << sizeof(b) << endl;
    cout << "sizeof(c): " << sizeof(c) << endl;
    cout << "sizeof(d): " << sizeof(d) << endl;

    // No issue.

    a* a1 = new a();
    cout << "a1: " << a1 << endl;
    delete a1;

    // No issue.

    b* b1 = new b();
    cout << "b1: " << b1 << endl;
    cout << "(a*) b1: " << (a*) b1 << endl;
    delete b1;

    // No issue.

    c* c1 = new c();
    cout << "c1: " << c1 << endl;
    cout << "(b*) c1: " << (b*) c1 << endl;
    cout << "(a*) c1: " << (a*) c1 << endl;
    delete c1;

    // No issue.

    d* d1 = new d();
    cout << "d1: " << d1 << endl;
    cout << "(c*) d1: " << (c*) d1 << endl;
    cout << "(b*) d1: " << (b*) d1 << endl;
    cout << "(a*) d1: " << (a*) d1 << endl;
    delete d1;

    // Doesn't crash, but may not produce the results you want.

    c1 = (c*) new d();
    delete c1;

    // Crashes due to passing an invalid address to the method which
    // frees the memory.

    d1 = new d();
    b1 = (b*) d1;
    cout << "d1: " << d1 << endl;
    cout << "b1: " << b1 << endl;
    delete b1;  

/*

    // This is similar to what's happening above in the "crash" case.

    char* buf = new char[32];
    cout << "buf: " << (void*) buf << endl;
    buf += 8;
    cout << "buf after adding 8: " << (void*) buf << endl;
    delete buf;
*/
}

Я не говорю, нужны ли вам виртуальные деструкторы или нет, хотя я думаю, что в целом иметь их - это хорошая практика. Я просто указываю причину, по которой у вас может произойти сбой, если ваш базовый класс (/ struct) не имеет vtable, а ваш производный класс (/ struct) имеет, и вы удаляете объект через базовый класс (/ struct) указатель. В этом случае адрес, который вы передаете свободной подпрограмме кучи, недействителен и, следовательно, является причиной сбоя.

Если вы запустите приведенный выше код, вы четко увидите, когда возникнет проблема. Когда указатель this базового класса (/ struct) отличается от указателя this производного класса (/ struct), вы столкнетесь с этой проблемой. В приведенном выше примере структуры a и b не имеют vtables. в структурах c и d есть таблицы vtables. Таким образом, указатель a или b на экземпляр объекта c или d будет зафиксирован для учета vtable. Если вы передадите этот указатель a или b для удаления, он выйдет из строя из-за того, что адрес недействителен для бесплатной процедуры кучи.

Если вы планируете удалить производные экземпляры, которые имеют vtables из указателей базового класса, вам необходимо убедиться, что у базового класса есть vtable. Один из способов сделать это - добавить виртуальный деструктор, который в любом случае может понадобиться для правильной очистки ресурсов.

person nickdu    schedule 23.03.2017

Базовое определение virtual состоит в том, что он определяет, можно ли переопределить функцию-член класса в его производных классах.

D-tor класса вызывается в основном в конце области видимости, но возникает проблема, например, когда мы определяем экземпляр в куче (динамическое размещение), мы должны удалить его вручную.

Как только инструкция будет выполнена, вызывается деструктор базового класса, но не производного.

Практический пример - это когда в поле управления вам нужно манипулировать исполнительными механизмами, исполнительными механизмами.

В конце области, если деструктор одного из силовых элементов (Actuator) не вызывается, будут фатальные последствия.

#include <iostream>

class Mother{

public:

    Mother(){

          std::cout<<"Mother Ctor"<<std::endl;
    }

    virtual~Mother(){

        std::cout<<"Mother D-tor"<<std::endl;
    }


};

class Child: public Mother{

    public:

    Child(){

        std::cout<<"Child C-tor"<<std::endl;
    }

    ~Child(){

         std::cout<<"Child D-tor"<<std::endl;
    }
};

int main()
{

    Mother *c = new Child();
    delete c;

    return 0;
}
person rekkalmd    schedule 20.05.2020

Любой класс, который унаследован публично, полиморфный или нет, должен иметь виртуальный деструктор. Другими словами, если на него может указывать указатель базового класса, его базовый класс должен иметь виртуальный деструктор.

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

person Syed H    schedule 02.01.2015
comment
Я бы сказал, что это необходимо только в том случае, если на него можно указать указатель базового класса и можно публично удалить. Но, думаю, не помешает выработать привычку добавлять виртуальные дторы на случай, если они могут понадобиться позже. - person underscore_d; 23.04.2016