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

  Юрий  | 

  |

  Обновл. 4 Сен 2021  | 

 110445

 ǀ   16 

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

Деструкторы

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

Когда объект автоматически выходит из области видимости или динамически выделенный объект явно удаляется с помощью ключевого слова 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 (333 оценок, среднее: 4,86 из 5)
Загрузка...

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

  1. Петр:

    долгое время считал что деструктор автоматически удаляет все, что создано в конктрукторе. Достаточно его просто создать. Недавно задумался что это не так, и впервые про это прочитал тут. Спасибо, где ж вы раньше были)

  2. ОЛЕГ-777:

    Не совсем понял фразу в тексте: "В рамках идиомы программирования RAII объекты, располагающие ресурсами, не должны быть динамически выделенными, так как деструкторы вызываются только при уничтожении объектов". Ведь деструкторы нужны в основном только при использовании динамической памяти, а тут написано, что "объекты не должны быть динамически выделенными". Как-то я запутался. Даже GOOGLE не помог.
    Кто бы мне объяснил, я бы был очень благодарен.

    1. Александр:

      Если объект создается в автоматической области памяти (напр. в функции), то при выходе из этой области он будет уничтожен деструктором автоматически, без вашего участия. Если же вы создаете объекты в динамической памяти (используя new), то освобождать ее вы должны сами операцией delete (это должно быть отражено в деструкторе вами, те дб написан нужный код). Почитайте "Язык программирования C++. Лекции и упражнения" Стивена Прата
      6-е издание, глава 12 — там все есть.

    2. Павел:

      Я так понял, что это про последний пример говорится:

      Если выделяешь объект динамически — сам должен его и очищать. И это не соответствует RAII.
      А в первом примере

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

  3. Алекчсандр:

    Коротко, емко, толково. Спасибо. Хотя все это и знал, но не могу не оставить положительный отзыв за грамотное и сжатое изложение.

    1. Фото аватара Юрий:

      Пожалуйста))

  4. Илья:

    Интересная стаття
    Много полезно для себя под черпнул из неё
    Спасибо вам за вашу роботу 🙂

    1. Фото аватара Юрий:

      Пожалуйста))

  5. Алексей:

    Отлично, теперь запомним, что надо убирать после себя.

    Спасибо)

  6. somebox:

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

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

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

  7. Александр:

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

    1. Фото аватара Юрий:

      Пожалуйста)

  8. kmish:

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

  9. Игорь:

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

    Должно быть

    1. Владимир:

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

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

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

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