RAII
Получение ресурса есть инициализация или RAII — это техника программирования на C++ [1] [2] , которая связывает жизненный цикл ресурса, который должен быть получен перед использованием (выделенная память в куче, поток выполнения, открытый сокет, открытый файл, заблокированный мьютекс, дисковое пространство, подключение к базе данных — всё, что существует в ограниченном количестве) с временем жизни объекта.
RAII гарантирует, что ресурс доступен любой функции, которая может обращаться к объекту (доступность ресурса является инвариантом класса , что исключает избыточные проверки во время выполнения). Также гарантируется, что все ресурсы освобождаются при завершении времени жизни управляющего объекта в порядке, обратном порядку их получения. Аналогично, если получение ресурса завершается неудачно (конструктор завершается исключением), все ресурсы, полученные каждым полностью сконструированным членом и базовым подобъектом, освобождаются в порядке, обратном порядку инициализации. Это использует основные возможности языка ( время жизни объекта , выход из области видимости , порядок инициализации и раскрутка стека ) для устранения утечек ресурсов и гарантии безопасности исключений. Другое название этой техники — Управление ресурсами с привязкой к области видимости (Scope-Bound Resource Management, SBRM), которое отражает базовый сценарий использования, когда время жизни объекта RAII завершается из-за выхода из области видимости.
RAII можно резюмировать следующим образом:
- инкапсулировать каждый ресурс в класс, где
-
- конструктор получает ресурс и устанавливает все инварианты класса или выбрасывает исключение, если это невозможно сделать,
- деструктор освобождает ресурс и никогда не выбрасывает исключения;
- всегда используйте ресурс через экземпляр класса RAII, который либо
-
- имеет автоматическую продолжительность хранения или временное время жизни само по себе, или
- имеет время жизни, ограниченное временем жизни автоматического или временного объекта.
|
Семантика перемещения позволяет передавать ресурсы и владение между объектами, внутри и вне контейнеров, а также между потоками, обеспечивая при этом безопасность ресурсов. |
(since 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 (since C++20) , и многие другие получают свои ресурсы в конструкторах (которые генерируют исключения при ошибках), освобождают их в деструкторах (которые никогда не генерируют исключения) и не требуют явной очистки.
|
Кроме того, стандартная библиотека предоставляет несколько RAII-обёрток для управления пользовательскими ресурсами:
|
(начиная с C++11) |
Примечания
RAII не применим к управлению ресурсами, которые не приобретаются до использования: время ЦПУ, доступность ядер, емкость кэша, емкость пула энтропии, пропускная способность сети, потребление электроэнергии, память стека. Для таких ресурсов конструктор класса C++ не может гарантировать доступность ресурса на протяжении времени жизни объекта, и должны использоваться другие средства управления ресурсами.