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