Коли використовувати віртуальні деструктори?


1486

Я чітко розумію більшість теорій OO, але одне, що мене дуже бентежить, - це віртуальні деструктори.

Я думав, що деструктора завжди викликають незалежно від того, що стосується кожного предмета ланцюга.

Коли ви повинні зробити їх віртуальними і чому?



146
Кожного деструктора вниз називають незважаючи ні на що. virtualпереконайтеся, що він починається вгорі, а не в середині.
Mooing Duck


@MooingDuck це дещо оманливий коментар.
Euri Pinhollow

1
@FranklinYu, добре, що ви запитали, тому що зараз я не бачу жодної проблеми з цим коментарем (крім спроби дати відповідь у коментарях).
Euri Pinhollow

Відповіді:


1572

Віртуальні деструктори корисні, коли ви можете потенційно видалити екземпляр похідного класу через вказівник на базовий клас:

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!

Оскільки деструктор Base не є virtualі bє Base*посилається на Derivedоб'єкт, delete bмає невизначений поведінка :

[In delete b], якщо статичний тип об'єкта, який потрібно видалити, відрізняється від його динамічного типу, статичний тип повинен бути базовим класом динамічного типу об'єкта, що видаляється, а статичний тип повинен мати віртуальний деструктор або поведінка не визначена .

У більшості реалізацій виклик до деструктора буде вирішений, як і будь-який невіртуальний код, що означає, що деструктор базового класу буде викликаний, але не той, який є похідним класом, що призведе до витоку ресурсів.

Підводячи підсумок, завжди робіть деструктори базових класів, virtualколи вони повинні маніпулювати поліморфно.

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

Докладніше про віртуальність та деструктор віртуального базового класу ви можете дізнатися в цій статті від Herb Sutter .


174
Це пояснило б, чому у мене були масові витоки на заводі, який я робив раніше. Зараз все має сенс. Спасибі
Лодле

8
Ну, це поганий приклад, оскільки немає даних членів. Що робити , якщо Baseі Derivedє всі автоматичні змінні для зберігання? тобто немає "спеціального" або додаткового спеціального коду для виконання в деструкторі. Чи нормально тоді взагалі не відмовлятись від написання деструкторів? Або похідний клас все ще матиме витік пам'яті?
bobobobo


28
Зі статті Herb Sutter: "Настанова №4: Деструктор базового класу повинен бути або загальнодоступним, і віртуальним, або захищеним і невіртуальним".
Sundae

3
Також із статті - "якщо ви видалите поліморфно без віртуального руйнівника, ви викликаєте жахливий привид" невизначеної поведінки ", привид, якого я особисто не хотів би зустріти навіть у помірно добре освітленій алеї, дуже дякую". lol
Бондолін

219

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

#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

Таким чином, знищення базового вказівника (який приймає розподіл на похідному об'єкті!) Слідує правилу руйнування, тобто спочатку Отримані, потім База. З іншого боку, немає нічого подібного до віртуального конструктора.


1
"віртуальний конструктор не можливий" означає, що вам не потрібно писати віртуальний конструктор самостійно. Побудова похідного об'єкта повинна слідувати ланцюгу побудови від похідної до основи. Тому вам не потрібно писати віртуальне ключове слово для вашого конструктора. Спасибі
Тунвір Рахман Тушер

4
@Murkantilism, "віртуальних конструкторів зробити не можна" справді правда. Конструктор не може бути позначений віртуальним.
cmeub

1
@cmeub, Але є ідіома досягти того, що ви хотіли б від віртуального конструктора. Дивіться parashift.com/c++-faq-lite/virtual-ctors.html
cape1232

@TunvirRahmanTusher, чи можете ви поясніть, чому називається Destructor Base ??
rimalonfire

. @Rimiro Його автоматичний на C ++ ви можете слідувати за посиланням stackoverflow.com/questions/677620 / ...
Tunvir Rahman Tusher

195

Оголосити деструктори віртуальними в базових класах поліморфних. Це Пункт 7 у Ефективному С ++ Скотта Майєрса . Далі Майєрс підсумовує, що якщо у класу є якась віртуальна функція, він повинен мати віртуальний деструктор, і що класи, не призначені для базових класів або не призначені для використання в поліморфних формах, не повинні оголошувати віртуальні деструктори.


14
+ "Якщо клас має яку-небудь віртуальну функцію, він повинен мати віртуальний деструктор, а класи, не призначені для базових класів або не призначені для використання в поліморфних формах, не повинні оголошувати віртуальні деструктори.": Чи є випадки, коли це має сенс порушити це правило? Якщо ні, чи має сенс змусити компілятора перевірити цю умову та видати помилку, чи це не задоволено?
Джорджо

@Giorgio Я не знаю жодних винятків із правила. Але я б не оцінював себе як експерта C ++, тому ви можете поставити це як окреме запитання. Попередження компілятора (або попередження від статичного інструмента аналізу) має для мене сенс.
Білл Ящірка

10
Класи можуть бути розроблені таким чином, щоб не видалятися через покажчик певного типу, але все ж мають віртуальні функції - типовим прикладом є інтерфейс зворотного виклику. Один не видаляє його реалізацію за допомогою вказівника інтерфейсу зворотного дзвінка, оскільки це лише для підписки, але у нього є віртуальні функції.
дасканді

3
@dascandy Саме так - та чи всі багато інших ситуацій, коли ми використовуємо поліморфну ​​поведінку, але не виконуємо управління зберіганням за допомогою покажчиків - наприклад, підтримання об'єктів автоматичної або статичної тривалості, при цьому вказівники використовуються лише як маршрути спостереження. Немає потреби / мети у впровадженні віртуального деструктора в будь-яких таких випадках. Оскільки ми просто цитуємо людей тут, я віддаю перевагу Саттеру зверху: "Настанова №4: Деструктор базового класу повинен бути або загальнодоступним, і віртуальним, або захищеним і невіртуальним". Остання гарантує, що хтось випадково намагається видалити за допомогою базового вказівника, відображається помилка їх шляхів
underscore_d

1
@Giorgio Насправді є хитрість, яку можна використати та уникнути віртуального виклику деструктора: прив’язати через посилання const похідний об'єкт до бази, наприклад const Base& = make_Derived();. У цьому випадку Derivedвикличеться деструктор prvalue, навіть якщо він не віртуальний, тому зберігається накладні витрати, введені vtables / vpointers. Звичайно, сфера застосування досить обмежена. Андрій Олександреску згадав про це у своїй книзі « Сучасний дизайн C ++» .
vsoftco

46

Також пам’ятайте, що видалення покажчика базового класу, коли немає віртуального деструктора, призведе до невизначеної поведінки . Щось, про що я дізнався нещодавно:

Як має поводитися переосмислювальне видалення в C ++?

Я використовую C ++ протягом багатьох років, і я все ще встигаю повіситися.


Я подивився на ваше запитання і побачив, що ви оголосили деструктор бази як віртуальний. Тож чи "видалення покажчика базового класу, коли немає віртуального деструктора, призведе до невизначеної поведінки", залишається дійсним стосовно вашого питання? Оскільки в цьому питанні, коли ви викликаєте delete, похідний клас (створений його новим оператором) спочатку перевіряється на сумісну версію. Оскільки він там знайшов його, його називали. Отже, ви не вважаєте, що було б краще сказати, що "видалення покажчика базового класу, коли немає деструктора, призведе до невизначеної поведінки"?
ubuntugod

Це майже те саме. Конструктор за замовчуванням не віртуальний.
BigSandwich

41

Зробіть деструктор віртуальним, коли ваш клас поліморфний.


13

Виклик деструктора за допомогою вказівника на базовий клас

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

Чи потрібно чітко заявляти ~Derived()у всіх похідних класах, навіть якщо це просто ~Derived() = default? Або це має на увазі мова (що дозволяє безпечно опускати)?
Ponkadoodle

@Wallacoloo ні, оголошуйте це лише тоді, коли це необхідно. Наприклад, щоб розмістити його в protectedрозділі або забезпечити його віртуальний за допомогою override.
Абікс

9

Мені подобається думати про інтерфейси та реалізацію інтерфейсів. В інтерфейсі говорити C ++ - це чистий віртуальний клас. Destructor є частиною інтерфейсу, який очікується впровадити. Тому деструктор повинен бути чисто віртуальним. Як щодо конструктора? Конструктор насправді не є частиною інтерфейсу, оскільки об'єкт завжди явно інстанціюється.


2
Це інша точка зору на одне й те саме питання. Якщо ми думаємо з точки зору інтерфейсів замість базового класу проти похідного класу, то це природний висновок: якщо це частина інтерфейсу, ніж зробити його віртуальним. Якщо це не так.
Драган Остожич

2
+1 для констатації подібності концепції OO щодо інтерфейсу та чистого віртуального класу C ++ . Стосовно деструктора очікується реалізація : це часто непотрібно. Якщо клас не керує таким ресурсом, як динамічно розподілена пам'ять (наприклад, не за допомогою інтелектуального вказівника), ручка файлу або ручка бази даних, використовуючи деструктор за замовчуванням, створений компілятором, добре в похідних класах. І зауважте, що якщо деструктор (або будь-яка функція) оголошено virtualв базовому класі, він автоматично virtualзнаходиться у похідному класі, навіть якщо він не оголошений так.
DavidRR

Тут пропускається найважливіша деталь, що деструктор не обов'язково є частиною інтерфейсу. Можна легко запрограмувати класи, які мають поліморфні функції, але які абонент не керує / забороняється видаляти. Тоді віртуальний деструктор не має мети. Звичайно, щоб забезпечити це, невіртуальний - можливо, за замовчуванням - деструктор повинен бути непублічним. Якби мені довелося здогадатися, я б сказав, що такі класи частіше використовуються всередині проектів, але це не робить їх менш актуальними як приклад / нюанс у всьому цьому.
підкреслюй_d

8

Віртуальне ключове слово для деструктора необхідне, коли ви хочете, щоб різні деструктори дотримувались належного порядку, поки об’єкти видаляються через покажчик базового класу. наприклад:

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

Якщо ваш деструктор базового класу є віртуальним, об'єкти будуть знищені в порядку (спочатку похідний об'єкт, потім базовий). Якщо ваш деструктор базового класу НЕ віртуальний, видаляється лише об’єкт базового класу (оскільки вказівник базового класу "Base * myObj"). Таким чином, відбудеться витік пам'яті для похідного об'єкта.


7

Щоб бути простим, Virtual destructor - це знищити ресурси в належному порядку, коли ви видаляєте вказівник базового класу, що вказує на похідний об'єкт класу.

 #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


Не має базового віртуального деструктора і виклик deleteбазового вказівника призводить до невизначеної поведінки.
Джеймс Адкісон

@JamesAdkison чому це призводить до невизначеної поведінки ??
rimalonfire

@rimiro Це те, що говорить стандарт . У мене немає копії, але посилання пересилає вас до коментаря, де хтось посилається на місцеположення в межах стандарту.
Джеймс Адкісон

@rimiro "Якщо видалення може бути виконане поліморфно через інтерфейс базового класу, то воно має поводитися практично і повинно бути віртуальним. Дійсно, мова вимагає цього - якщо ви видалите поліморфно без віртуального деструктора, ви викликаєте жахливий спектр "невизначена поведінка", привид, якого я особисто не хотів би зустріти навіть у помірно добре освітленій алеї, дуже дякую ". ( gotw.ca/publications/mill18.htm ) - Герб Саттер
Джеймс Адкісон

4

Віртуальні деструктори базового класу - «найкраща практика» - ви завжди повинні використовувати їх, щоб уникнути (важко виявити) витоків пам’яті. Використовуючи їх, ви можете бути впевнені, що всі деструктори в ланцюжку спадкування ваших класів називаються (у належному порядку). Наслідування базового класу за допомогою віртуальної деструктора також робить деструктор класу успадковування автоматично віртуальним (тому вам не доведеться повторно вводити "віртуальний" у декларації деструктора класу спадкування).


4

Якщо ви використовуєте shared_ptr(тільки shared_ptr, не унікальний_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

Хоча це можливо, я б заважав комусь користуватися цим. Накладні витрати віртуального деструктора незначні, і це просто дає можливість зіпсувати, особливо менш досвідчений програміст, який цього не знає. Це маленьке virtualключове слово може врятувати вас від безлічі агонії.
Міхал Штейн

3

Що таке віртуальний деструктор або як використовувати віртуальний деструктор

Деструктор класу - це функція з такою ж назвою класу, що передує ~, що перерозподіляє пам'ять, виділену класом. Для чого нам потрібен віртуальний деструктор

Дивіться наступний зразок з деякими віртуальними функціями

Зразок також розповідає, як можна перетворити лист у верхній або нижній

#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


2

коли вам потрібно викликати деструктор похідного класу з базового класу. вам потрібно оголосити віртуальний деструктор базового класу в базовому класі.


2

Я думаю, що суть цього питання полягає у віртуальних методах та поліморфізмі, а не деструкторі конкретно. Ось більш чіткий приклад:

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.

А тепер вам слід зрозуміти, коли використовувати віртуальні деструктори.


Ні, це лише відновлює суттєві основи віртуальних функцій, повністю ігноруючи нюанс того, коли / чому деструктор повинен бути таким - що не настільки інтуїтивно, отже, чому ОП задало це питання. (Крім того, навіщо тут непотрібний динамічний розподіл? Просто зробіть B b{}; A& a{b}; a.foo();. Перевірка NULL- що має бути nullptr- перед deleteін. - з неправильним відступом - не потрібно: delete nullptr;визначається як неоперація . Якщо що-небудь, ви повинні перевірити це перед тим, як дзвонити ->foo(), оскільки в іншому випадку може виникнути невизначена поведінка, якщо newякимось чином не вдалося.)
підкреслити_d

2
Безпечно дзвонити deleteза NULLвказівником (тобто, вам не потрібен if (a != NULL)охоронець).
Джеймс Адкісон

@SaileshD Так, я знаю. Про це я сказав у своєму коментарі
Джеймс Адкісон

1

Я подумав, що було б корисно обговорити "невизначене" поведінку або, принаймні, "краху" невизначеної поведінки, що може статися під час видалення через базовий клас (/ структуру) без віртуального деструктора, а точніше без 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;
*/
}

Я не підказую, потрібні вам віртуальні деструктори чи ні, хоча я взагалі думаю, що це є гарною практикою їх мати. Я просто вказую на причину того, що у вас може виникнути збій, якщо ваш базовий клас (/ структура) не має vtable, а ваш похідний клас (/ структура) робить, а ви видаляєте об'єкт через базовий клас (/ структура) покажчик. У цьому випадку адреса, яку ви передаєте у вільну процедуру купи, є недійсною, і, отже, причиною збоїв.

Якщо запустити вищевказаний код, ви чітко побачите, коли виникає проблема Коли цей покажчик базового класу (/ структура) відрізняється від цього покажчика похідного класу (/ структура), ви збираєтеся зіткнутися з цією проблемою. У наведеному вище прикладі структури a і b не мають vtables. у структур c і d є vtables. Таким чином, покажчик a або b на екземпляр об'єкта ac або d буде зафіксований для обліку vtable. Якщо ви передасте цей a або b покажчик для його видалення, він вийде з ладу через те, що адреса є недійсною для безкоштовного розпорядку купи.

Якщо ви плануєте видалити похідні екземпляри, у яких є vtables з покажчиків базового класу, вам потрібно переконатися, що базовий клас має vtable. Один із способів зробити це - додати віртуальний деструктор, який, можливо, хочете, щоб правильно очистити ресурси.


0

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

D-tor класу називається в основному в кінці області, але є проблема, наприклад, коли ми визначаємо екземпляр на Heap (динамічне розподілення), ми повинні його видалити вручну.

Як тільки інструкція виконується, викликається деструктор базового класу, але не для похідного.

Пратичний приклад - коли в полі управління вам доведеться маніпулювати ефекторами, виконавчими механізмами.

Наприкінці сфери застосування, якщо руйнінг одного з силових елементів (Привід) не буде викликаний, це матиме фатальні наслідки.

#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;
}

-1

Будь-який клас, який успадковується публічно, поліморфно чи ні, повинен мати віртуальний деструктор. Інакше кажучи, якщо на нього можна вказати вказівником базового класу, його базовий клас повинен мати віртуальний деструктор.

Якщо віртуальний, виведений деструктор класу викликається, то конструктор базового класу. Якщо не віртуально, викликається лише деструктор базового класу.


Я б сказав, що це потрібно лише "якщо на нього можна вказати вказівником базового класу" і його можна публічно видалити. Але я гадаю, що не завадить звикати додавати віртуальні двигуни на випадок, якщо вони стануть потрібними пізніше.
підкреслюй_d
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.