Відповіді:
Це ще важливіше для інтерфейсу. Будь-який користувач вашого класу, ймовірно, матиме покажчик на інтерфейс, а не вказівник на конкретну реалізацію. Коли вони приходять до його видалення, якщо деструктор невіртуальний, вони викличуть інструмент деструктора інтерфейсу (або за умовчанням, наданий компілятором, якщо ви його не вказали), а не деструктор похідного класу. Миттєве витоку пам'яті.
Наприклад
class Interface
{
virtual void doSomething() = 0;
};
class Derived : public Interface
{
Derived();
~Derived()
{
// Do some important cleanup...
}
};
void myFunc(void)
{
Interface* p = new Derived();
// The behaviour of the next line is undefined. It probably
// calls Interface::~Interface, not Derived::~Derived
delete p;
}
[expr.delete]/
: ... if the static type of the object to be deleted is different from its dynamic type, ... the static type shall have a virtual destructor or the behavior is undefined. ...
. Це все ще буде невизначено, якби Derived використовував неявно створений деструктор.
Відповідь на ваше запитання часто, але не завжди. Якщо ваш абстрактний клас забороняє клієнтам викликати видалення по вказівнику на нього (або якщо він так говорить у своїй документації), ви не можете оголошувати віртуальний деструктор.
Ви можете заборонити клієнтам викликати видалення на вказівник на нього, зробивши захищений його деструктор. Працюючи так, цілком безпечно і розумно опустити віртуальний деструктор.
Зрештою, у вас не буде таблиці віртуальних методів, і в кінцевому підсумку ви будете сигналізувати своїм клієнтам про намір зробити його незнімним через вказівник на нього, тому ви справді маєте підстави не оголошувати його віртуальним у цих випадках.
[Дивіться пункт 4 у цій статті: http://www.gotw.ca/publications/mill18.htm ]
Я вирішив зробити кілька досліджень і спробувати узагальнити ваші відповіді. Наступні питання допоможуть вам вирішити, який тип деструктора вам потрібен:
Я сподіваюся, що це допомагає.
* Важливо зауважити, що в C ++ немає можливості позначати клас як остаточний (тобто не підкласифікований), тому у випадку, якщо ви вирішили оголосити свого деструктора невіртуальним та загальнодоступним, не забудьте прямо застерегти своїх колег-програмістів від походить від вашого класу.
Список літератури:
Так, це завжди важливо. Отримані класи можуть виділяти пам'ять або містити посилання на інші ресурси, які потрібно буде очистити при знищенні об'єкта. Якщо ви не надаєте своїм інтерфейсам / абстрактним класам віртуальних деструкторів, то кожного разу, коли ви видаляєте похідний екземпляр класу через ручку базового класу, ваш деструктор похідного класу не буде викликаний.
Отже, ви відкриваєте потенціал для витоку пам'яті
class IFoo
{
public:
virtual void DoFoo() = 0;
};
class Bar : public IFoo
{
char* dooby = NULL;
public:
virtual void DoFoo() { dooby = new char[10]; }
void ~Bar() { delete [] dooby; }
};
IFoo* baz = new Bar();
baz->DoFoo();
delete baz; // memory leak - dooby isn't deleted
Це не завжди потрібно, але я вважаю це гарною практикою. Що це робить, чи дозволяє похідний об'єкт безпечно видалити через покажчик базового типу.
Так, наприклад:
Base *p = new Derived;
// use p as you see fit
delete p;
неправильно сформований, якщо у Base
нього немає віртуального деструктора, тому що він буде намагатися видалити об'єкт так, ніби він був Base *
.
shared_ptr
намагаються видалити об'єкт так, ніби він був Base *
- він пам’ятає тип речі, з якої ви створили його. Дивіться посилане посилання, зокрема біт, на якому написано "Деструктор викличе видалення з тим самим покажчиком у комплекті з його початковим типом, навіть коли T не має віртуального деструктора або недійсний".
Це не лише добра практика. Це правило №1 для будь-якої ієрархії класів.
Тепер для Чому. Візьміть типову ієрархію тварин. Віртуальні деструктори проходять віртуальну розсилку так само, як і будь-який інший метод виклику. Візьмемо наступний приклад.
Animal* pAnimal = GetAnimal();
delete pAnimal;
Припустимо, що Animal - це абстрактний клас. Єдиний спосіб, коли C ++ знає належного деструктора для виклику, це за допомогою віртуального методу відправки. Якщо деструктор не віртуальний, він просто викличе деструктор Animal і не знищить жодних об'єктів у похідних класах.
Причиною зробити деструктор віртуальним у базовому класі є те, що він просто знімає вибір із похідних класів. Їх деструктор за замовчуванням стає віртуальним.
Відповідь проста: вам потрібно, щоб вона була віртуальною, інакше базовий клас не був би повним поліморфним класом.
Base *ptr = new Derived();
delete ptr; // Here the call order of destructors: first Derived then Base.
Ви б віддали перевагу вищевказаному видаленню, але якщо деструктор базового класу не є віртуальним, викличеться лише деструктор базового класу, а всі дані у похідному класі залишаться невибраними.
delete p
посилається на невизначену поведінку. Телефонувати не гарантованоInterface::~Interface
.