Получение ресурса есть инициализация
Получение ресурса есть инициализация или RAII, это техника программирования C++[1][2], которая связывает жизненный цикл ресурса, который должен быть получен перед использованием (выделенная память в куче, поток выполнения, открытый сокет, открытый файл, заблокированный мьютекс, дисковое пространство, подключение к базе данных, всё, что существует в ограниченном количестве) с временем жизни объекта.
RAII гарантирует, что ресурс доступен любой функции, которая может иметь доступ к объекту (доступность ресурса это инвариант класса, что устраняет избыточные тесты во время выполнения). Это также гарантирует, что все ресурсы высвобождаются, когда время жизни их управляющего объекта заканчивается, в порядке, обратном получению. Аналогичным образом, если получение ресурсов завершается с ошибкой (конструктор завершает работу с исключением), все ресурсы, полученные каждым полностью сконструированным элементом и базовым подобъектом, освобождаются в порядке, обратном инициализации. При этом используются основные функции языка (время жизни объекта, выход из области видимости, порядок инициализации и раскручивание стека), чтобы исключить утечку ресурсов и гарантировать безопасность исключений. По другому эта техника называется Управление Ресурсами С Привязкой К Области Видимости (SBRM - Scope-Bound Resource Management), по основному варианту использования, когда время жизни объекта RAII заканчивается из-за выхода из области видимости.
RAII можно резюмировать следующим образом:
- инкапсулировать каждый ресурс в класс, где
- конструктор получает ресурс и устанавливает все инварианты класса или генерирует исключение, если это невозможно сделать,
- деструктор освобождает ресурс и никогда не генерирует исключений;
- всегда использовать ресурс через экземпляр класса RAII, который либо
- имеет автоматическую длительность хранения или временное время жизни, или
- имеет время жизни, которое ограничено временем жизни автоматического или временного объекта.
|
Семантика перемещения позволяет безопасно передавать право собственности на ресурсы между объектами, между областями видимости, а также внутри и вне потоков, сохраняя при этом безопасность ресурсов. |
(начиная с C++11) |
Классы с open()/close(), lock()/unlock(), или init()/copyFrom()/destroy() методами являются типичным примером не-RAII классов.
std::mutex m;
void bad()
{
m.lock(); // получает мьютекс
f(); // если f() генерирует исключение, мьютекс никогда
// не будет освобождён
if(!everything_ok()) return; // мьютекс не освобждён при раннем выходе из функции
m.unlock(); // если bad() достигает этого оператора мьютекс
// будет освобождён
}
void good()
{
std::lock_guard<std::mutex> lk(m); // класс RAII: получение мьютекса есть инициализация
f(); // если f() генерирует исключение, мьютекс
// будет освобождён
if(!everything_ok()) return; // мьютекс освобождён в случае раннего выхода
// из функции
} // если good() завершается корректно мьютекс
// будет освобождён
Стандартная библиотека
Классы бибилиотеки C++, которые управляют собстенными ресурсами, следуют RAII: std::string, std::vector, std::jthread (начиная с C++20) и многие другие получают свои ресурсы в конструкторах (которые генерируют исключения при ошибках), освобождают их в деструкторах (никогда не генерирующих исключений) и не требуют явной очистки.
|
Кроме того, стандартная библиотека предлагает несколько RAII обёрток для управления предоставленными пользователями ресурсами:
|
(начиная с C++11) |
Примечание
RAII не применяется к управлению ресурсами, которые не были получены перед использованием: процессорное время, количество ядер и объём кэш-памяти, ёмкость энтропийного пула, пропускная способность сети, потребление электроэнергии, память стека.