Объекты классов могут на протяжении всего своего существования использовать различные ресурсы - динамически выделенная память, файлы, сетевые подключения и т.д. В этом случае в 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 несет ответственность за освобождение памяти,
что и происходит в конце функции.