Виртуальный деструктор в C++
В языке программирования C++ деструктор полиморфного базового класса должен объявляться виртуальным. Только так обеспечивается корректное разрушение объекта производного класса через указатель на соответствующий базовый класс.
Рассмотрим следующий пример.
#include using namespace std; // Вспомогательный класс class Object < public: Object() < cout ~Object() < cout >; // Базовый класс class Base < public: Base() < cout virtual ~Base() < cout virtual void print() = 0; >; // Производный класс class Derived: public Base < public: Derived() < cout ~Derived() < cout void print() <> Object obj; >; int main ()
В функции main указателю на базовый класс присваивается адрес динамически создаваемого объекта производного класса Derived. Затем через этот указатель объект разрушается. При этом наличие виртуального деструктора базового класса обеспечивает вызовы деструкторов всех классов в ожидаемом порядке, а именно, в порядке, обратном вызовам конструкторов соответствующих классов.
Вывод программы с использованием виртуального деструктора в базовом классе будет следующим:
Base::ctor() Object::ctor() Derived::ctor() Derived::dtor() Object::dtor() Base::dtor()
Уничтожение объекта производного класса через указатель на базовый класс с невиртуальным деструктором дает неопределенный результат. На практике это выражается в том, что будет разрушена только часть объекта, соответствующая базовому классу. Если в коде выше убрать ключевое слово virtual перед деструктором базового класса, то вывод программы будет уже иным. Обратите внимание, что член данных obj класса Derived также не разрушается.
Base::ctor() Object::ctor() Derived::ctor() Base::dtor()
Когда же следует объявлять деструктор виртуальным? Cуществует правило — если базовый класс предназначен для полиморфного использования, то его деструктор должен объявляться виртуальным. Для реализации механизма виртуальных функций каждый объект класса хранит указатель на таблицу виртуальных функций vptr, что увеличивает его общий размер. Обычно, при объявлении виртуального деструктора такой класс уже имеет виртуальные функции, и увеличения размера соответствующего объекта не происходит.
Если же базовый класс не предназначен для полиморфного использования (не содержит виртуальных функций), то его деструктор не должен объявляться виртуальным.
Зачем нужен виртуальный деструктор? [дубликат]
Виртуальный деструктор необходим, чтобы избежать возможной утечки ресурсов или другого неконтролируемого поведения объекта, в логику работы которого включен вызов деструктора.
#include using namespace std; struct Base < Base() < cout ~Base() < cout >; struct Derived: public Base < Derived() < cout ~Derived() < cout >; int main()
Что вы могли ожидать на выходе:
Base() Derived() ~Derived() ~Base()
Что может произойти (может, потому что, в общем случае, это undefined behaviour):
Base() Derived() ~Base()
Для устранения данной проблемы необходимо деструктор класса родителя объявить виртуальным ( virtual ~Base() ), что позволит компилятору добраться до деструктора наследника по таблице виртуальных функций.
Зачем нужен виртуальный деструктор при наследовании?
Насколько я понял, виртуальный деструктор нужен, чтобы в наследованном классе можно было вызвать (прямо оттуда) деструктор базового, а т.к. простой деструктор наследованный класс не понимает, то нужно создавать виртуальный. И всё это, чтобы не было никакой утечки памяти. Так ли я понял суть?
Отслеживать
758 1 1 золотой знак 8 8 серебряных знаков 22 22 бронзовых знака
задан 29 июл 2013 в 23:33
hamsternik hamsternik
521 3 3 золотых знака 9 9 серебряных знаков 29 29 бронзовых знаков
2 ответа 2
Сортировка: Сброс на вариант по умолчанию
Не, немного не так. Деструктор базового класса из деструктора производного вызывать не надо (да и нельзя), он сам вызовется автоматически. Проблема в другом: деструктор производного класса может быть не вызван!
Представьте себе такую ситуацию:
class Person < ~Person() < >// деструктор не виртуальный >; class Spy : public Person < Gadget* gadgets; public: Spy() < gadgets = new Gadget[10]; >~Spy() < delete[] gadgets; >>; std::vector citizens; // наполнить значениями for (Person* citizen : citizens) delete citizen;
Что случится, если в списке будет один Spy ? А вот что: при уничтожении объекта типа Spy по указателю типа Person* вызовется невиртуальный деструктор ~Person . Значит, память под массив gadgets не будет освобождена. Вот вам и утечка памяти.
На самом деле, кроме утечки памяти может произойти любая другая неприятность, ведь деструктор, на который вы рассчитывали, не вызовется! Например, может не закрыться файл, и при следующей попытке его открыть программа вылетит. Или не отпустится мьютекс, и при попытке его получить программа зависнет. Ну и ещё куча всяких катастроф может произойти.
Хуже того, по стандарту отсутствие виртуального деструктора в данном случае является undefined behaviour, то есть, программа имеет право сделать что угодно: отформатировать винчестер, признаться в любви к вашей химичке через «Вконтакте» или подлить валерьянки в миску с Вискасом.
Да, а в C деструкторов нету вовсе.
Необходимость в использовании виртуальных деструкторов и их правильное использование

Добрый вечер, у меня несколько вопросов по виртуальным деструкторам.
1) В данном примере не используются виртуальные деструкторы, правильно ли это? По идее динамическая память тут не выделяется поэтому можно обойтись без деструкторов, но в аналогичном примере из моего второго вопроса (класс Brain) виртуальные деструкторы почему-то используются. Где правильно? И в каких случаях действительно нужны виртуальные деструкторы?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
#include #include using namespace std; class Human{ protected: string name; int age; char sex; public: /* v Ваш код v */ Human(string name, int age, char sex) { this-> name = name; this-> age = age; this-> sex = sex; } virtual void Show() { cout name endl age endl sex endl; } }; class Coder: public Human{ protected: float IQ; public: /* v Ваш код v */ Coder(string name, int age, char sex, float IQ):Human(name, age, sex) { this-> IQ = IQ; } void Show() { cout IQ; } }; int main(){ Human *human = new Human("Tom", 21, 'M'); Human *coder = new Coder("Alice", 19, 'F', 150.6f); human->Show(); coder->Show(); return 0; }
2) У базового класса есть виртуальный деструктор virtual ~Brain(), у первого наследника вирт. деструктор ~Legs() плюс в конце высвобождается память с помощью delete legs; аналогично со вторым наслеником — ~Eyes() и delete eyes. Виртуальный деструктор нужен, чтобы при удалении объекта удалялась не только часть класса наследника, но и базового. В связи с чем вопрос, нужно ли в конце писать delete brain? И почему?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
#include using namespace std; class Brain{ public: void Start(){ cout "Робот успешно запущен\n"; } virtual bool CheckLegs(){ return false; } virtual bool CheckEyes(){ return false; } /*v Ваш код v*/ virtual ~Brain(){ cout "Робот выключен\n"; } }; class Legs: public Brain{ public: bool CheckLegs(){ cout "Проверка опорнодвигательной системы. \n"; return true; } /*v Ваш код v*/ ~Legs(){ cout "Движение робота не возможно\n"; } }; class Eyes: public Brain{ public: bool CheckEyes(){ cout "Проверка работоспособности камеры. \n"; return true; } /*v Ваш код v*/ ~Eyes(){ cout "Робот ничего не видит\n"; } }; int main(){ Brain *brain = new Brain; Brain *legs = new Legs; Brain *eyes = new Eyes; if (legs->CheckLegs() && eyes->CheckEyes()){ brain->Start(); } delete legs; delete eyes; return 0; }
Лучшие ответы ( 2 )
94731 / 64177 / 26122
Регистрация: 12.04.2006
Сообщений: 116,782
Ответы с готовыми решениями:
Правильное использование конструкторов и деструкторов
#include "stdafx.h" #include <iostream> #include <conio.h> using namespace std; class Worker.
использование деструкторов
добрый день!! спасибо всем на форуме, кто помогает изучать язык с 0 толковыми объяснениями. ведь.
Использование конструторов и деструкторов
Вычислить значение выражения 10^y — x^y + x^n , если х>0. С помощью конструторов и деструкторов. .
Использование конструкторов и деструкторов
Решить в консольном режиме: Дан набор из N вещественных чисел. Проверить, образует ли данный набор.
Комп_Оратор)
![]()
8950 / 4704 / 629
Регистрация: 04.12.2011
Сообщений: 14,000
Записей в блоге: 16
Сообщение от cofo 
Виртуальный деструктор нужен, чтобы при удалении объекта удалялась не только часть класса наследника, но и базового.
Виртуальный деструктор нужен, чтобы при удалении объекта удалялась не только часть принадлежащая базовому классу, но и часть принадлежащая наследнику.
Дело в том что деструктор базового будет вызван в любом случае, а виртуальный деструктор будет вызван именно для того чем наследник отличился. От батька.
В Вашем случае это имеет смысл как учебный тест — посмотреть сообщения, их порядок вывода. Потому что они действительно ничего не освобождают тут.
Хуже другое. Виртуальные методы бессмысленны и беспощадны. Лучше было бы определить в базовом метод
virtual bool self_test();
(self это оказывается ыуда )
тогда в наследниках legs протянут ноги, а eyes вытаращат глазья. То есть при вызове self_test через указатели базового класса Вам не нужно будет знать что именно и кто вызывает. Вы будете уверены, что вот тот кто вызывает, так он уж вызовет именно то что ему надо. То есть вытаращенных ног и вытянутых глаз не случится. Во всяком случае не должно.
А указатели надо освобождать (вернее их память). Там и деструкторы вызовутся вдобавок. Хотя main и так всё уничтожит, но для порядку — вызывайте. Причина та же что и у любых указателей на динамическую память: надо.
![]()
![]()
11697 / 6376 / 1724
Регистрация: 18.10.2014
Сообщений: 16,080

Сообщение было отмечено Croessmah как решение
Решение
Сообщение от cofo 
Виртуальный деструктор нужен, чтобы при удалении объекта удалялась не только часть класса наследника, но и базового.
Виртуальный деструктор нужен только для одного: чтобы правильно удалять (при помощи delete ) объект-наследник через указатель на его базу.
1 2 3 4 5
struct Base { . }; struct Derived : Base { . }; . Base *p = new Derived; delete p; // Нужен виртуальный деструктор `~Base::Base()`
Если в такой ситуации деструктор невиртуален, то поведение не определено. Нет и быть не может никаких объяснений о том, что произойдет в таком случае, из разряда «чтобы удалялась не только часть класса наследника, но и базового», или «будет утечка памяти», или еще чего. Это все какая-то белиберда. Поведение просто не определено.
Ни в одном из ваших вариантов такого удаления нет. Поэтому формально нигде вам виртуальный деструктор не нужен.
Другое дело, что в ваших примерах присутствуют утечки памяти, потому что вы вообще не удаляете некоторые объекты. Но к виртуальным деструкторам эта тема отношения не имеет, по крайней мере пока.
Сообщение от cofo 
В связи с чем вопрос, нужно ли в конце писать delete brain?
Сообщение от cofo 
Что значит «почему»? Создали объект — не забывайте и удалить. В программе количество delete должно быть равно количеству new : сколько создали, столько и удаляем.
Комп_Оратор)
![]()
8950 / 4704 / 629
Регистрация: 04.12.2011
Сообщений: 14,000
Записей в блоге: 16
Сообщение от TheCalligrapher 
Если в такой ситуации деструктор невиртуален, то поведение не определено. Нет и быть не может никаких объяснений о том, что произойдет в таком случае, из разряда «чтобы удалялась не только часть класса наследника, но и базового», или «будет утечка памяти», или еще чего. Это все какая-то белиберда. Поведение просто не определено.
Строго говоря, да. Поведение будет определяться поведением системы в ситуации, когда указатель типа T1, приведён к типу Т2, а потом на нём запущен delete. В части освобождения памяти, потому как вызова «левого» деструктора не будет. Деструктор базового класса не создаст проблем, а вот несовпадение размеров создаст.
Я написал то, что написал не для проявления глубины знаний. Её и нет если честно. Я пытался объяснить ситуацию в терминах ТС. Так как то что он пытается сказать, свидетельствует о том, что термины ему знакомы, а ситуация (модель в которой они работают) непонятна. Судя по его молчанию, он не переварил сказанного мною. Сказанного Вами и подавно. Или занят очень.
В любом случае, Ваше замечание верно и я благодарен Вам за помощь. Спасибо.
Форумчанин
![]()
![]()
8215 / 5045 / 1437
Регистрация: 29.11.2010
Сообщений: 13,453
Сообщение от TheCalligrapher 
В программе количество delete должно быть равно количеству new: сколько создали, столько и удаляем.
Поправочка: если не использовать smart pointer-ы, разумеется.
Ушел с форума
![]()
16473 / 7436 / 1187
Регистрация: 02.05.2013
Сообщений: 11,617
Записей в блоге: 1
Сообщение от cofo 
Где правильно? И в каких случаях действительно нужны виртуальные деструкторы?
Везде, где объект удаляется через указатель на его предка (так называемое полиморфное
удаление), у предка должен быть (MUST HAVE. ) виртуальный деструктор. Нарушение этого
требования ведет к неопределенному поведению — UB.
Очень правильно TheCalligrapher написал выше — не должно и не может быть никаких
предположений или объяснений на этот счет, даже если программа успешно компилируется и на
этапе выполнения не вылезает никаких ошибок. Это все до поры. Легко можно привести
реальный, не надуманный, пример, когда нарушение этого правила ведет к аварийному
завершению работы программы на большинстве популярных компиляторов.
2549 / 1208 / 358
Регистрация: 30.11.2013
Сообщений: 3,826
MrGluck, нету поправочки — смарт-указатели инкапсулируют delete
![]()
8739 / 4317 / 960
Регистрация: 15.11.2014
Сообщений: 9,760
Сообщение от Убежденный 
Легко можно привести
реальный, не надуманный, пример, когда нарушение этого правила ведет к аварийному
завершению работы программы на большинстве популярных компиляторов.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
#include struct base { virtual void ololo()const = 0 ; }; struct der: base { virtual void ololo()const {} }; struct trololo: base { virtual void ololo()const {} }; struct example: der, trololo {}; int main() { std::cout "Hello, world!\n"; std::cout "example: undefined behavior\n"; std::cout "it resulted in abortion signal"; std::cout std::endl; trololo* p = new example; delete p; }
Комп_Оратор)
![]()
8950 / 4704 / 629
Регистрация: 04.12.2011
Сообщений: 14,000
Записей в блоге: 16

Сообщение было отмечено Croessmah как решение
Решение
Сообщение от Убежденный 
Очень правильно TheCalligrapher написал выше — не должно и не может быть никаких
предположений или объяснений на этот счет
С одной стороны да. А с другой стороны, посмотрите что пишет ТС. Для него сейчас пора ознакомления с виртуальным наследованием. Причём, несколько преждевременная, так как с указателями он знаком ещё совсем слабо и задаёт вопросы о применении delete для элементарной ситуации. В чём польза предваряющего ознакомления? В том же в чём от предваряющего объявления. То есть, линкуется лучше потом. В голове.
Более того. Знакомясь с концепцией мне, например, вначале важно ответить на вопросы: в чём смысл? какая задача решается?, какова цель? Это всё один вопрос. Иначе я просто не смогу учить. Конечно, нельзя опережать события слишком.
Посмотрите как ТС применил виртуальное наследование. Оно бессмысленно. И пока он не поймёт как оно работает, работу виртуального метода «деструктор» объяснить невозможно. Но объяснить можно на языке близком к его языку. А говоря точно, нужно либо главами писать, либо сформулировать «вещь в себе» которых полно в книгах и которую разберёшь когда их перечитаешь. Тут есть дилемма. Я не призываю опускаться до мычания и сюсюкания, но некоторая мера условности оправдана. Всё к чему мы прикасаемся требует многократного возвращения и уточнения. То есть, если судьба, то человек к этому ещё вернётся.