Для чего используется деструкторы при создание программ
Конструкторы и деструкторы
Конструкторы
Конструктор — функция, предназначенная для инициализации объектов класса. Рассмотрим класс date :
Если конструктор требует аргументы, их следует указать:
Если необходимо обеспечить несколько способов инициализации объектов класса, задается несколько конструкторов:
Конструкторы подчиняются тем же правилам относительно типов параметров, что и перегруженные функции. Если конструкторы существенно различаются по типам своих параметров, то компилятор при каждом использовании может выбрать правильный:
Одним из способов сократить количество перегруженных функций (в том числе и конструкторов) является использование значений по умолчанию.
Конструктор по умолчанию
При создании объекта вызывается конструктор, за исключением случая, когда объект создается как копия другого объекта этого же класса, например:
Однако имеются случаи, в которых создание объекта без вызова конструктора осуществляется неявно:
Во всех этих случаях транслятор не вызывает конструктора для вновь создаваемого объекта:
Вместо этого в них копируется содержимое объекта-источника:
Конструктор копии
Как правило, при создании нового объекта на базе уже существующего происходит поверхностное копирование, то есть копируются те данные, которые содержит объект-источник. При этом если в объекте-источнике имеются указатели на динамические переменные и массивы, или ссылки, то создание копии объекта требует обязательного дублирования этих объектов во вновь создаваемом объекте. С этой целью вводится конструктор копии, который автоматически вызывается во всех перечисленных случаях. Он имеет единственный параметр — ссылку на объект-источник:
Деструкторы
Определяемый пользователем класс имеет конструктор, который обеспечивает надлежащую инициализацию. Для многих типов также требуется обратное действие. Деструктор обеспечивает соответствующую очистку объектов указанного типа. Имя деструктора представляет собой имя класса с предшествующим ему знаком «тильда»
. Так, для класса X деструктор будет иметь имя
Поля, имеющие тип класса
Конструкторы членов класса всегда выполняются до конструктора класса, в котором эти члены описаны. Порядок выполнения конструкторов для членов класса определяется порядком объявления членов класса. Если конструктору члена класса требуются аргументы, этот член с нужными аргументами указывается в списке инициализации. Деструкторы вызываются в обратном порядке.
BestProg
Деструкторы. Определение деструктора. Общедоступные и приватные деструкторы. Примеры использования деструкторов. Отличия между конструкторами и деструкторами
Содержание
Поиск на других ресурсах:
1. Какое назначение деструктора в классе?
Деструктор – это обратная по отношению к конструктору функция.
Имя деструктора совпадает с именем класса, перед которым следует символ ‘
2. Пример использования общедоступного деструктора
Общий код модуля, в котором объявляется структура и класс.
Демонстрация использования данного класса в другом методе.
Да, можно. Такой деструктор называется приватным деструктором.
4. В каких случаях целесообразно объявлять приватные деструкторы?
Использование приватных деструкторов целесообразно в тех случаях, когда обычным пользователям запрещается освобождать память для прежде созданных объектов (уничтожать раньше созданные объекты).
5. Какие ограничения возникают при работе с объектами класса, если в классе объявлен приватный деструктор?
Если в классе объявлен приватный деструктор, то возникают следующие ограничения:
Это связано с тем, что такие объекты в дальнейшем невозможно будет уничтожить.
Например. Пусть задан класс, в котором объявлен приватный деструктор.
Если попробовать создать объект класса в другом программном коде
то компилятор выдаст ошибку
6. Может ли деструктор иметь параметры?
Деструктор не может иметь параметров.
7. Какие основные отличия между использованием конструкторов и деструкторов в классах?
При использовании в классе, между конструктором и деструктором можно определить следующие основные отличия:
12.9 – Деструкторы
Деструктор – это еще один особый вид функции-члена класса, которая выполняется при уничтожении объекта этого класса. В то время как конструкторы предназначены для инициализации класса, деструкторы предназначены для помощи в очистке.
Однако если ваш объект класса содержит какие-либо ресурсы (например, динамическую память или дескриптор файла или базы данных), или если вам нужно выполнить какое-либо действие до того, как объект будет уничтожен, деструктор – идеальное место для этого, поскольку обычно это последнее, что выполняется перед уничтожением объекта.
Именование деструктора
Как и у конструкторов, у деструкторов есть особые правила именования:
Обратите внимание, что правило 2 подразумевает, что для каждого класса может существовать только один деструктор, поскольку нет возможности перегружать деструкторы, так как они не могут отличаться друг от друга на основе аргументов.
Как правило, вы не должны вызывать деструктор явно (так как при уничтожении объекта он будет вызываться автоматически), поскольку вы редко когда захотите очистить объект более одного раза. Однако деструкторы могут безопасно вызывать другие функции-члены, поскольку объект не уничтожится до тех пор, пока не выполнится деструктор.
Пример деструктора
Давайте посмотрим на простой класс, который использует деструктор:
Совет
Если вы скомпилируете приведенный выше пример и получите следующую ошибку:
Эта программа дает следующий результат:
В первой строке мы создаем экземпляр нового объекта класса IntArray с именем ar и передаем длину 10. Это вызывает конструктор, который динамически выделяет память для массива-члена. Здесь мы должны использовать динамическое выделение памяти, потому что во время компиляции не знаем, какова будет длина массива (это решает вызывающий).
В конце main() объект ar выходит за пределы области видимости. Это вызывает вызов деструктора
Время выполнения конструктора и деструктора
Как упоминалось ранее, конструктор вызывается при создании объекта, а деструктор – при уничтожении объекта. В следующем примере мы используем инструкции cout внутри конструктора и деструктора, чтобы показать это:
Эта программа дает следующий результат:
RAII (Resource Acquisition Is Initialization, получение ресурса есть инициализация) – это метод программирования, при котором использование ресурсов привязано к времени жизни объектов с автоматической продолжительностью (например, нединамически выделяемые объекты). В C++ RAII реализован через классы с конструкторами и деструкторами. Ресурс (например, память, дескриптор файла или базы данных и т.д.) обычно приобретается в конструкторе объекта (хотя он может быть получен после создания объекта, если это имеет смысл). Затем этот ресурс можно использовать, пока объект жив. Ресурс освобождается в деструкторе при уничтожении объекта. Основное преимущество RAII заключается в том, что он помогает предотвратить утечку ресурсов (например, не освобождение памяти), поскольку все объекты, содержащие ресурсы, очищаются автоматически.
Класс IntArray в примере выше является примером класса, который реализует RAII – выделение в конструкторе, освобождение в деструкторе. std::string и std::vector – классы стандартной библиотеки, которые следуют принципу RAII – динамическая память приобретается при инициализации и автоматически очищается при уничтожении.
Предупреждение о функции exit()
Резюме
Как видите, когда конструкторы и деструкторы используются вместе, ваши классы могут инициализироваться и очищаться после себя, без необходимости выполнения программистом какой-либо специальной работы! Это снижает вероятность ошибки и упрощает использование классов.
Виртуальные функции и деструктор
Когда-то давным давно я собирался и даже обещал написать про механизм виртуальных функций относительно деструкторов. Теперь у меня наконец появилось свободное время и я решил воплотить эту затею в жизнь. На самом деле эта мини-статья служит «прологом» к моей следующей статье. Но я постарался изложить доходчиво и понятно основные моменты по текущей теме. Если вы чувствуете, что еще недостаточно разобрались в механизме виртуальных вызовов, то, возможно, вам следует для начала прочитать мою предыдущую статью.
Сразу же, как обычно, оговорюсь, что: 1) статья моя не претендует на полноту изложения материала; 2) мегапрограммеры ничего нового здесь не узнают; 3) материал не новый и давно описан во многих книгах, но если явно об этом не прочитать и самому специально не задумываться, то можно о некоторых моментах даже не подозревать (до поры, до времени). Также прошу прощения за надуманные примеры 🙂
Виртуальные деструкторы
Если вы уже знаете и умеете использовать виртуальные функции, то просто обязаны знать, когда и зачем нужны виртуальные деструкторы. Иначе нижеследующий текст был написан именно для вас.
Основное правило: если у вас в классе присутствует хотя бы одна виртуальная функция, деструктор также следует сделать виртуальным. При этом не следует забывать, что деструктор по умолчанию виртуальным не будует, поэтому следует объявить его явно. Если этого не сделать, у вас в программе почти наверняка будут утечки памяти (memory leaks). Чтобы понять почему, опять же много ума не надо. Рассмотрим несколько примеров.
В первом случае создадим объект производного класса в стеке:
using std :: cout ;
using std :: endl ;
Всем ясно, что вывод программы будет следующим:
потому что сначала конструируется базовая часть класса, затем производная, а при разрушении наоборот — сначала вызывается деструктор производного класса, который по окончании своей работы вызывает по цепочке деструктор базового. Это правильно и так должно быть.
Попробуем теперь создать тот же объект в динамической памяти, используя при этом указатель на объект базового класса (код классов не изменился, поэтому привожу только код функции main()):
int main ( )
<
A * pA = new B ;
delete pA ;
return EXIT_SUCCESS ;
>
На сей раз конструируется объект так, как и надо, а при разрушении происходит утечка памяти, потому как деструктор производного класса не вызывается:
Происходит это потому, что удаление производится через указатель на базовый класс и для вызова деструктора компилятор использует раннее связывание. Деструктор базового класса не может вызвать деструктор производного, потому что он о нем ничего не знает. В итоге часть памяти, выделенная под производный класс, безвозвратно теряется.
Чтобы этого избежать, деструктор в базовом классе должен быть объявлен как виртуальный:
using std :: cout ;
using std :: endl ;
int main ( )
<
A * pA = new B ;
delete pA ;
return EXIT_SUCCESS ;
>
Теперь-то мы получим желаемый порядок вызовов:
Происходит так потому, что отныне для вызова деструктора используется позднее связывание, то есть при разрушении объекта берется указатель на класс, затем из таблицы виртуальных функций определяется адрес нужного нам деструктора, а это деструктор производного класса, который после своей работы, как и полагается, вызывает деструктор базового. Итог: объект разрушен, память освобождена.
Виртуальные функции в деструкторах
Давайте для начала рассмотрим ситуацию с вызовом виртуальных функций внутри класса. Предположим, что у нас есть Кот, который просит покушать мяуканьем, а затем приступает к процессу 🙂 Так поступают многие коты, но не Чеширский! Чеширский, как известно, мало того что вечно улыбается, так еще и довольно разговорчив, поэтому мы научим его говорить, переопределив метод speak():
using std :: cout ;
using std :: endl ;
class Cat
<
public :
void askForFood ( ) const
<
speak ( ) ;
eat ( ) ;
>
virtual void speak ( ) const < cout "Meow! " ; >
virtual void eat ( ) const < cout "*champing*" endl ; >
> ;
class CheshireCat : public Cat
<
public :
virtual void speak ( ) const < cout "WTF?! Where \' s my milk? =) " ; >
> ;
delete cats [ 0 ] ; delete cats [ 1 ] ;
return EXIT_SUCCESS ;
>
Вывод этой программы будет следующим:
Ordinary Cat: Meow! *champing*
Cheshire Cat: WTF?! Where’s my milk? =) *champing*
Рассмотрим код более подробно. Есть класс Cat с парой виртуальных методов, один из которых переопределен в производном CheshireCat. Но всё самое интересное происходит в методе askForFood() класса Cat.
Как видно, метод всего лишь содержит вызовы двух других методов, однако конструкция speak() в данном контексте эквивалента this->speak(), то есть вызов происходит через указатель, а значит — будет использовано позднее связывание. Вот почему при вызове метода askForFood() через указатель на CheshireCat мы видим то, что и хотели: механизм виртуальных функций работает исправно даже несмотря на то, что вызов непосредственно виртуального метода происходит внутри другого метода класса.
А теперь самое интересное: что будет, если попытаться воспользоваться этим в деструкторе? Модернизируем код так, чтобы при деструкции наши питомцы прощались, кто как умеет:
using std :: cout ;
using std :: endl ;
class Cat
<
public :
virtual
Cat ( ) < sayGoodbye ( ) ; >
void askForFood ( ) const
<
speak ( ) ;
eat ( ) ;
>
virtual void speak ( ) const < cout "Meow! " ; >
virtual void eat ( ) const < cout "*champing*" endl ; >
virtual void sayGoodbye ( ) const < cout "Meow-meow!" endl ; >
> ;
class CheshireCat : public Cat
<
public :
virtual void speak ( ) const < cout "WTF?! Where \' s my milk? =) " ; >
virtual void sayGoodbye ( ) const < cout "Bye-bye! (:" endl ; >
> ;
delete cats [ 0 ] ; delete cats [ 1 ] ;
return EXIT_SUCCESS ;
>
Можно ожидать, что, как и в случае с вызовом метода speak(), будет выполнено позднее связывание, однако это не так:
Ordinary Cat: Meow! *champing*
Cheshire Cat: WTF?! Where’s my milk? =) *champing*
Meow-meow!
Meow-meow!
Почему? Да потому что при вызове виртуальных методов из деструктора компилятор использует не позднее, а раннее связывание. Если подумать, зачем он делает именно так, всё становится очевидным: нужно просто рассмотреть порядок конструирования и разрушения объектов. Все помнят, что конструирование объекта происходит, начиная с базового класса, а разрушение идет в строго обратном порядке. Таким образом, когда мы создаем объект типа CheshireCat, порядок вызовов конструкторов/деструкторов будет таким:
Если же мы захотим внутри деструктора
Cat() совершить виртуальный вызов метода sayGoodbye(), то фактически попытаемся обратиться к той части объекта, которая уже была разрушена.
Мораль: если в вашей голове витают помыслы выделить какой-то алгоритм «зачистки» в отдельный метод, переопределяемый в производных классах, а затем виртуально вызывать его в деструкторе, у вас ничего не выйдет.
Деструктор (программирование)
Деструктор (программирование)
Дестру́ктор — специальный метод класса, служащий для деинициализации объекта (например освобождения памяти).
Содержание
Деструктор в Delphi
Метод Free вначале проверяет существует ли уничтожаемый объект, а затем вызывает деструктор. Этот прием позволяет избегать ошибок, возникающих при обращении к несуществующему объекту.
Деструктор в С++
Виртуальный деструктор
Практически всегда деструктор делается виртуальным. Делается это для того, чтобы корректно (без утечек памяти) уничтожались объекты не только заданного класса, а и любого производного от него. Например: в игре уровни, звуки и спрайты могут создаваться загрузчиком, а уничтожаться — менеджером памяти, для которого нет разницы между уровнем и спрайтом.
Пусть (на C++) есть тип Father и порождённый от него тип Son :
Нижеприведённый код является некорректным и приводит к утечке памяти.
Однако, если сделать деструктор Son виртуальным:
Деструктор в UML
См. также
Полезное
Смотреть что такое «Деструктор (программирование)» в других словарях:
Деструктор класса — Деструктор специальный метод класса, служащий для деинициализации объекта (например освобождения памяти). Содержание 1 Деструктор в Delphi 2 Деструктор в С++ 3 Виртуальный деструктор … Википедия
Деструктор — У этого термина существуют и другие значения, см. Деструкторы (экология). Деструктор специальный метод класса, служащий для деинициализации объекта (например освобождения памяти). Содержание 1 Деструктор в Delphi 2 Деструктор в С++ … Википедия
Конструктор (программирование) — У этого термина существуют и другие значения, см. Конструктор. В объектно ориентированном программировании конструктор класса (от англ. constructor, иногда сокращают ctor) специальный блок инструкций, вызываемый при создании объекта.… … Википедия
Объектно-ориентированное программирование на Python — Объектно ориентированное программирование на Python программирование на Python с использованием парадигмы ООП: с самого начала Python проектировался как объектно ориентированный язык программирования[1]. Содержание 1 Введение 1.1 … Википедия
Объектно-ориентированное программирование на Питоне — С самого начала Питон проектировался как объектно ориентированный язык программирования [1]. Содержание 1 Введение 1.1 Принципы ООП … Википедия
Класс (программирование) — У этого термина существуют и другие значения, см. Класс. Класс в программировании набор методов и функций. Другие абстрактные типы данных метаклассы, интерфейсы, структуры, перечисления характеризуются какими то своими, другими… … Википедия
Класс (объектно-ориентированное программирование) — Класс, наряду с понятием «объект», является важным понятием объектно ориентированного подхода в программировании (хотя существуют и бесклассовые объектно ориентированные языки, например, Прототипное программирование). Под классом подразумевается… … Википедия
Правило трёх (C++ программирование) — Правило трёх (также известное как «Закон Большой Тройки» или «Большая Тройка») правило в C++, гласящее, что если класс или структура определяет один из следующих методов, то они должны явным образом определить все три метода[1]: Деструктор… … Википедия
Правило трех (C++ программирование) — Правило трёх (также известное как «Закон Большой Тройки» или «Большая Тройка») правило в C++, гласящее, что если класс или структура определяет один из следующих методов, то они должны явным образом определить все три метода[1]: Деструктор… … Википедия
Блок (программирование) — У этого термина существуют и другие значения, см. Блок. Блок кода, блок команд, блок инструкций часть кода, которая сгруппирована и воспринимается как единое целое (похоже на параграф). Блоки могут состоять из одного или нескольких… … Википедия