Menu
CoddyTech

Глубокое погружение в деструкторы

Часть раздела Объектно-ориентированное программирование путешествия по C++ на Coddy. Урок 24 из 104.

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

Деструктор имеет то же имя, что и класс, с префиксом в виде тильды ~. Он не принимает параметров и не имеет типа возвращаемого значения:

class FileHandler {
    std::string filename;
    int* buffer;
public:
    FileHandler(std::string name, size_t size) 
        : filename(name), buffer(new int[size]) {
        std::cout << "Opening " << filename << "\n";
    }
    ~FileHandler() {
        delete[] buffer;
        std::cout << "Closing " << filename << "\n";
    }
};

Деструкторы автоматически вызываются в следующих ситуациях:

  • Объекты стека выходят из области видимости
  • Для объекта, выделенного в куче, вызывается delete
  • Время жизни временного объекта заканчивается
void example() {
    FileHandler f1("data.txt", 100);    // Вызван конструктор
    FileHandler* f2 = new FileHandler("log.txt", 50);
    delete f2;                           // Деструктор вызван для f2
}   // Деструктор вызван для f1 (выходит из области видимости)

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

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

challenge icon

Задание

Легко

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

Вы создадите два файла, чтобы организовать код:

  • Session.h: определите класс Session, представляющий активный пользовательский сеанс с динамически выделяемыми данными. В вашем классе должны быть:
    • Приватные поля: username (строка), sessionId (целое число) и указатель на массив целых чисел с именем activityLog, который отслеживает действия пользователя, а также logSize для хранения размера массива
    • Конструктор, принимающий имя пользователя, идентификатор сеанса и размер журнала. Он должен выделить массив журнала действий в куче, инициализировать все элементы значением 0 и вывести "Session <sessionId> started for <username>"
    • Деструктор, освобождающий выделенную память и выводящий "Session <sessionId> ended for <username>"
    • Метод logActivity(int activityCode), сохраняющий код действия в следующей доступной ячейке (текущая позиция отслеживается внутри класса); если журнал уже заполнен, вызов игнорируется, поэтому количество записей никогда не превышает размер журнала
    • Метод getActivityCount(), возвращающий количество записанных действий
    • Метод getUsername(), возвращающий имя пользователя
  • main.cpp: продемонстрируйте поведение деструктора в разных сценариях. Считайте из ввода имя пользователя и размер журнала (каждое значение находится на отдельной строке), затем:
    • Создайте блок области видимости с помощью фигурных скобок { } и внутри него создайте размещённый в стеке объект Session с именем пользователя из ввода, идентификатором сеанса 101 и размером журнала из ввода. Запишите два действия с кодами 1 и 2, затем выведите "Stack session activities: <count>". Когда блок завершится, деструктор должен запуститься автоматически.
    • После блока выведите "Stack session destroyed"
    • Создайте размещённый в куче объект Session с помощью new, указав имя пользователя "Guest", идентификатор сеанса 202 и размер журнала 3. Запишите одно действие с кодом 5, выведите "Heap session activities: <count>", а затем явно удалите объект с помощью delete.
    • После удаления выведите "Heap session destroyed"

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

Подключите заголовочный файл в main.cpp с помощью #include "Session.h".

Попробуйте сами

#include <iostream>
#include <string>
#include "Session.h"

using namespace std;

int main() {
    // Прочитать ввод
    string username;
    int logSize;
    getline(cin, username);
    cin >> logSize;
    
    // TODO: Создайте блок области видимости с фигурными скобками { }
    // Внутри блока:
    //   - Создайте Session, выделенный в стеке, с введённым именем пользователя, ID сессии 101 и введённым размером лога
    //   - Зарегистрируйте две активности с кодами 1 и 2
    //   - Print "Stack session activities: <count>"
    // Когда блок заканчивается, деструктор выполняется автоматически
    
    // TODO: After the block, print "Stack session destroyed"
    
    // TODO: Создайте Session, выделенный в куче, используя new
    //   - Имя пользователя: "Guest", ID сессии: 202, размер лога: 3
    //   - Зарегистрируйте одну активность с кодом 5
    //   - Print "Heap session activities: <count>"
    //   - Удалите сессию
    
    // TODO: After deletion, print "Heap session destroyed"
    
    return 0;
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование

Потренируйтесь самостоятельно: Онлайн-компилятор C++