Какие примеры использования идиомы RAII в C++, помимо умных указателей?

«Какие примеры использования идиомы RAII в C++, помимо умных указателей?» — вопрос из категории Управление памятью, который задают на 25% собеседований C/C++ Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

RAII — фундаментальная идиома C++, которую я применяю для управления любыми ресурсами с ограниченным временем жизни. Вот ключевые примеры из моего опыта:

  1. Управление файлами (std::fstream, std::ifstream, std::ofstream): Самый наглядный пример. Файл открывается в конструкторе и гарантированно закрывается в деструкторе, даже при выбросе исключения.

    {
        std::ofstream logFile("app.log", std::ios::app);
        logFile << "Application startedn";
        // ... возможен throw
    } // logFile.~ofstream() — файл закрывается здесь автоматически
  2. Синхронизация потоков (std::lock_guard, std::unique_lock, std::scoped_lock): Эти обертки захватывают мьютекс в конструкторе и освобождают в деструкторе, что исключает deadlock из-за забытого unlock().

    std::mutex g_cacheMutex;
    void updateCache() {
        std::lock_guard<std::mutex> lock(g_cacheMutex); // Захват
        // Безопасная модификация кэша
    } // Автоматическое освобождение мьютекса при выходе из области видимости
  3. Временные измерения: Я создаю простой RAII-класс ScopedTimer, который запоминает время начала в конструкторе и выводит продолжительность в деструкторе — очень удобно для профилирования.

    class ScopedTimer {
        std::chrono::time_point<std::chrono::high_resolution_clock> start;
        std::string name;
    public:
        ScopedTimer(std::string_view timerName) : name(timerName) {
            start = std::chrono::high_resolution_clock::now();
        }
        ~ScopedTimer() {
            auto end = std::chrono::high_resolution_clock::now();
            auto dur = std::chrono::duration<double, std::milli>(end - start);
            std::cout << name << ": " << dur.count() << " msn";
        }
    };
  4. Графические ресурсы (в обертках над OpenGL/DirectX): В своих графических проектах я создаю классы Texture, ShaderProgram, VertexBuffer, которые загружают ресурсы в конструкторе (через glGenTextures и т.п.) и освобождают их в деструкторе (через glDeleteTextures). Это делает управление GPU-памятью безопасным и исключает утечки.