Урок №120. Деструкторы

  Юрий  | 

    | 

  Обновл. 14 Июн 2019  | 

 12947

 ǀ   7 

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

Деструкторы

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

Когда объект автоматически выходит из области видимости или динамически выделенный объект явно удаляется с помощью ключевого слова delete, вызывается деструктор класса (если он существует) для выполнения необходимой очистки до того, как объект будет удалён из памяти. Для простых классов (тех, которые только инициализируют значения обычных переменных-членов) деструктор не нужен, так как C++ автоматически выполнит очистку самостоятельно.

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

Имена деструкторов


Так же, как и конструкторы, деструкторы имеют свои правила, которые касаются их имён:

   Деструктор должен иметь то же имя, что и класс, со знаком тильда (~) в самом начале.

   Деструктор не может принимать аргументы.

   Деструктор не имеет типа возврата.

Из второго правила вытекает ещё одно правило: для каждого класса может существовать только один деструктор, так как возможности перегрузить деструкторы как функции — нет и отличаться друг от друга аргументами они не могут.

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

Пример использования деструктора на практике

Рассмотрим простой класс с деструктором:

Результат выполнения программы выше:

The value of element 7 is 8

В первой строке функции main() мы создаём новый объект класса Massiv с именем arr и передаём длину (length) 15. Это приводит к вызову конструктора, который динамически выделяет память для массива класса (m_array). Мы должны здесь использовать динамическое выделение, поскольку на момент компиляции мы не знаем длину массива (это значение нам передаёт caller).

В конце функции main() объект arr выходит из области видимости. Это приводит к вызову деструктора ~Massiv() и к удалению массива, который мы выделили ранее в конструкторе!

Выполнение конструкторов и деструкторов


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

Результат выполнения программы выше:

Constructing Another 1
1
Constructing Another 2
2
Destructing Another 2
Destructing Another 1

Обратите внимание, Another 1 уничтожается после Another 2, так как мы удалили pObject до завершения выполнения функции main(), тогда как объект object не был удален до конца main().

Идиома программирования RAII

Идиома RAII (англ. «Resource Acquisition Is Initialization» = «Получение ресурсов есть инициализация») — это идиома объектно-ориентированного программирования, при которой использование ресурсов привязывается к времени жизни объектов с автоматической продолжительностью. В C++ идиома RAII реализуется через классы с конструкторами и деструкторами. Ресурс (например: память, файл или база данных) обычно приобретается в конструкторе объекта (хотя этот ресурс может быть получен и после создания объекта, если в этом есть смысл). Затем этот ресурс можно использовать, пока объект жив. Ресурс освобождается в деструкторе при уничтожении объекта. Основным преимуществом RAII является то, что это помогает предотвратить утечку ресурсов (например, память, которая не была освобождена), так как все объекты, содержащие ресурсы, автоматически очищаются.

В рамках идиомы программирования RAII объекты, располагающие ресурсами, не должны быть динамически выделенными, так как деструкторы вызываются только при уничтожении объектов. Для объектов, выделенных из стека, это происходит автоматически, когда объект выходит из области видимости, поэтому нет необходимости беспокоиться о том, что ресурс в конечном итоге не будет очищен. Однако с динамически выделенными объектами, которые выделяются из кучи, уже пользователь несет ответственность за очистку: если он забыл её выполнить, деструктор вызываться не будет, и память как для объекта класса, так и для управляемого ресурса будет потеряна — произойдёт утечка памяти!

Класс Massiv в начале этого урока является примером класса, который реализует принципы RAII: выделение в конструкторе, освобождение в деструкторе. std::string и std::vector — это примеры классов из стандартной библиотеки С++, которые следуют принципам RAII: динамическая память выделяется при инициализации и автоматически освобождается при уничтожении.

Правило: Используйте идиому программирования RAII и не выделяйте объекты вашего класса динамически.

Предупреждение о функции exit()


Если вы используете функцию exit(), то ваша программа завершится, и никакие деструкторы не будут вызваны. Будьте осторожны, если в таком случае вы полагаетесь на свои деструкторы для выполнения необходимой работы по очистке (например, перед тем, как выйти, вы записываете что-нибудь в журнальный файл или в базу данных).

Заключение

Используя конструкторы и деструкторы, ваши классы могут выполнять инициализацию и очистку после себя автоматически, без вашего участия! Это уменьшает вероятность возникновения ошибок и упрощает процесс использования классов.

Оценить статью:

Звёзд: 1Звёзд: 2Звёзд: 3Звёзд: 4Звёзд: 5 (61 оценок, среднее: 4,87 из 5)
Загрузка...

Комментариев: 7

  1. Аватар somebox:

    А если мы используем массивы типа std::array или std::vector, деструкторы создавать надо? Ведь, если я правильно помню из предыдущих уроков, эти типы автоматически высвобождают выделенную для них память.

    1. Аватар Анастасия:

      Вы правильно помните. Эти объекты уже созданы в виде классов, все конструкторы и деструкторы для них уже прописаны в файлах, которые мы через #include включаем в наш код. В уроке выше тоже про это говорится: " std::string и std::vector — это примеры классов из стандартной библиотеки С++, которые следуют принципам RAII: динамическая память выделяется при инициализации и автоматически освобождается при уничтожении."

  2. Аватар Александр:

    Спасибо Юрий! Ты молодец!

    1. Юрий Юрий:

      Пожалуйста)

  3. Аватар kmish:

    Спасибо. Все супер!

  4. Аватар Игорь:

    Почему здесь:

    Должно быть

    1. Аватар Владимир:

      Внутри assert мы пишем условие которое должно быть ИСТИНО, в противном случае мы получаем предупреждение

      Поэтому тут все логично, мы проверяем, что длина создаваемого массива больше 0:

Добавить комментарий

Ваш E-mail не будет опубликован. Обязательные поля помечены *