Паттерн RAII
Тема дорожной карты · Modern C++ Developer
RAII (Resource Acquisition Is Initialization) — центральная идиома C++, без которой невозможно писать надёжный код на этом языке. Суть проста: время жизни ресурса привязывается к времени жизни объекта на стеке. Конструктор захватывает ресурс, деструктор его освобождает, а компилятор гарантирует вызов деструктора при любом выходе из области видимости — включая исключения и ранние return. Именно RAII делает C++ языком без сборщика мусора, но с детерминированным управлением памятью, файлами, сокетами и блокировками.
Как это работает
Каждый объект с автоматическим временем хранения уничтожается в момент выхода из блока, в котором он объявлен, причём в порядке, обратном созданию. Если конструктор класса открывает файл, захватывает мьютекс или выделяет память, то деструктор обязан выполнить обратную операцию. Программисту не нужно помнить про освобождение: компилятор вставляет вызовы деструкторов на всех путях выполнения, включая раскрутку стека при выброшенном исключении.
Классические примеры из стандартной библиотеки — std::unique_ptr, std::lock_guard, std::fstream, std::vector. Все они владеют чем-то (памятью, блокировкой, дескриптором файла, буфером) и гарантируют корректное освобождение. Собственный RAII-класс пишется по той же схеме: захват в конструкторе, освобождение в деструкторе, а копирование либо запрещается (= delete), либо заменяется перемещением, чтобы у ресурса всегда был ровно один владелец.
class FileHandle {
FILE* f_;
public:
explicit FileHandle(const char* path) : f_(std::fopen(path, "rb")) {
if (!f_) throw std::runtime_error("open failed");
}
~FileHandle() { std::fclose(f_); }
FileHandle(const FileHandle&) = delete;
FileHandle& operator=(const FileHandle&) = delete;
};
Важное следствие — правило «нуля» и «пяти». Если класс владеет ресурсом через RAII-обёртки (умные указатели, контейнеры), ему не нужны собственные деструктор и операции копирования. Если же класс сам управляет сырым ресурсом, придётся аккуратно определить все пять специальных функций.
Когда применять
RAII применяется всегда, когда есть парная операция «получить — вернуть»: память и delete, fopen и fclose, lock и unlock, socket и close, транзакция и commit/rollback, SetCursor и восстановление прежнего курсора. Особенно ценна идиома в коде с исключениями: без RAII каждая функция превращается в лестницу try/catch, где легко пропустить один из путей. Оборачивайте в RAII любые ресурсы сторонних C-библиотек — OpenSSL, libcurl, SQLite, Win32 API. Для одноразового кода удобны обёртки вроде std::unique_ptr с пользовательским делитером или gsl::finally, чтобы не заводить отдельный класс.
Типичные ошибки
Самая частая проблема — исключение, выброшенное из деструктора. Во время раскрутки стека это приводит к std::terminate, поэтому деструкторы должны быть noexcept и обрабатывать ошибки освобождения молча или через логирование. Вторая ошибка — забытые операции копирования: компилятор сгенерирует их по умолчанию, и два объекта дважды освободят один дескриптор. Третья — временный объект вместо именованной переменной: строка std::lock_guard<std::mutex>(m); объявляет переменную с именем m, а не захватывает мьютекс, и блокировка не происходит. Наконец, RAII не спасает от циклических ссылок std::shared_ptr — здесь нужен std::weak_ptr или пересмотр модели владения.
Связанные понятия
Полезные ресурсы
🔤 Английские термины в этой теме
Слова из этой темы, которые русскоязычные часто произносят неверно. Кнопка 🔊 произносит голосом браузера.
- recipe /ˈrɛsəpi/ «рЕсипи», три слога разбор: Структура доков: Getting started, Reference, Guides, FAQ
- thrown /θroʊn/ с «θ» разбор: Читаем стектрейсы: exception, caused by, at line
- release /rɪˈliːs/ ударение на второй слог, «релис», а не «релиз» с русским «з» разбор: Версии и числа: two point oh, semver
- variable /ˈvɛəriəbl/ ударение на первый слог, а не «вэриЭйбл» разбор: Как читать код вслух
- should /ʃʊd/ l немая разбор: Язык RFC: MUST, SHOULD, MAY (RFC 2119)