Управление ресурсами. Идиома RAII

Последнее обновление: 18.03.2023

Объекты классов могут на протяжении всего своего существования использовать различные ресурсы - динамически выделенная память, файлы, сетевые подключения и т.д. В этом случае в C++ применяется так называемый принцип/идиома RAII (resource acquisition is initialization). RAII предполагает, что получение ресурса производится при инициализации объекта. А освобождение ресурса производится в деструкторе объекта.

#include <iostream>

class IntArray
{
public:
    IntArray(unsigned size) : data{ new int[size] } {}  // выделяем память
    ~IntArray()
    {
        if(data)
        {
            std::cout << "Freeing memory..." << std::endl;
            delete[] data;      // освобождаем память
        }
    }
    // Удаляем конструктор копирования и оператор присваивания
    IntArray(const IntArray&) = delete;
    IntArray& operator=(const IntArray&) = delete;

    // оператор индексирования для доступа к элементам
    int& operator[](unsigned index) { return data[index]; }

    // возвращаем инкапсулированный ресурс
    int* get() const { return data; }
    // передаем ресурс другогому объекту
    int* release()
    {
        int* result = data;
        data = nullptr;
        return result;
    }
private:
    int* data;
};

int main()
{   
    const unsigned count {5};   // количество элементов
    IntArray values{count};     // создаем объект, который управляет ресурсом

    // изменяем элементы динамического массива
    for (unsigned i {}; i < count; ++i)
    {
        values[i] = i;
    }
    // выводим элементы динамического массива на консоль
    for (unsigned i {}; i < count; ++i)
    {
        std::cout << values[i] << "\t";
    }
    std::cout << std::endl;
}

Здесь определен класс IntArray, который условно представляет массив чисел int и который управляет некоторым ресурсом. В данном случае ресурс представляет динамическую память, выделенную для хранения массива чисел int. Получение динамической памяти происходит в конструкторе объекта, а освобождение в деструкторе.

IntArray(unsigned size) : data{ new int[size] } {}  // выделяем память
~IntArray()
{
    std::cout << "Freeing memory..." << std::endl;
    delete[] data;      // освобождаем память
}

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

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

IntArray(const IntArray&) = delete;
IntArray& operator=(const IntArray&) = delete;

Для обращения к элементам динамического массива определен оператор индексирования [], а для получения непосредственно указателя - функция get.

Стоит отметить функцию release, которая позволяет передать указатель на управления во вне, в том числе другой объект. В этом случае сбрасывае указатель в nullptr, а обязанность освободить память ляжет на внешней код, который получает этот указатель:

int* release()
{
    int* result = data;
    data = nullptr;
    return result;
}

В функции main создаем один объект IntArray:

int main()
{   
    const unsigned count {5};   // количество элементов
    IntArray values{count};     // создаем объект, который управляет ресурсом

В итоге в конструкторе выделяется память, а после завершения функции main у IntArray вызывается деструктор, который освобождает память. Консольный вывод программы:

0       1       2       3       4
Freeing memory...

Посмотрим на применение функции release():

int main()
{   
    const unsigned count {5};   // количество элементов
    IntArray array{count};     // создаем объект, который управляет ресурсом

    // изменяем элементы динамического массива
    for (unsigned i {}; i < count; ++i)
    {
        array[i] = i;
    }

    // получаем указатель
    int* data = array.release(); // теперь функция main обязана освободить память

    for (unsigned i {}; i < count; ++i)
    {
        std::cout << data[i] << "\t";
    }
    std::cout << std::endl;

    //освобожадем память
    delete[] data;
}

Здесь указатель на динамического массива через функцию release передается в переменную data. После этого функция main несет ответственность за освобождение памяти, что и происходит в конце функции.

Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850