← Назад к вопросам

Что такое RAII?

1.2 Junior🔥 301 комментариев
#Язык C++

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI21 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

# RAII: Resource Acquisition Is Initialization

RAII — это фундаментальный паттерн в C++, который привязывает управление ресурсами к жизненному циклу объектов. Это одна из самых мощных инноваций языка, обеспечивающая безопасность памяти и надёжность.

Основная идея

Ресурсы захватываются в конструкторе, освобождаются в деструкторе.

Этот паттерн гарантирует:

  • Утечки памяти невозможны (деструктор вызовется ВСЕГДА)
  • Исключения обработаны правильно
  • Не нужно вручную отслеживать освобождение ресурсов

Базовый пример: File RAII

// ❌ БЕЗ RAII (C-style, опасно)
void process_file_bad() {
    FILE* f = fopen("data.txt", "r");
    
    // Если здесь исключение — файл не закроется!
    if (some_error_condition) {
        throw std::runtime_error("Oops!");
        // fclose(f) НИКОГДА не вызовется
    }
    
    // А что если раньше возвращаемся?
    if (file_empty) {
        return;  // утечка файлового дескриптора!
    }
    
    fclose(f);  // может быть, может не быть
}

// ✅ С RAII (безопасно)
class FileWrapper {
    FILE* handle;
    
public:
    FileWrapper(const char* path) {
        handle = fopen(path, "r");
        if (!handle) {
            throw std::runtime_error("Cannot open file");
        }
    }
    
    ~FileWrapper() {
        if (handle) {
            fclose(handle);
        }
    }
    
    // Запрещаем копирование (ресурс не может быть дублирован)
    FileWrapper(const FileWrapper&) = delete;
    FileWrapper& operator=(const FileWrapper&) = delete;
    
    // Разрешаем перемещение (transfer ownership)
    FileWrapper(FileWrapper&& other) noexcept 
        : handle(other.handle) {
        other.handle = nullptr;
    }
};

void process_file_good() {
    FileWrapper f("data.txt");  // RAII: acquire resource
    
    // Исключение? Деструктор f всё равно вызовется
    if (some_error) throw std::runtime_error("Error");
    
    // Ранний return? Деструктор вызовется
    if (file_empty) return;
    
    // Конец функции? Деструктор вызовется
}  // Здесь f.~FileWrapper() вызывается автоматически

Примеры RAII в стандартной библиотеке

1. std::lock_guard / std::unique_lock

void critical_section() {
    std::mutex mu;
    
    // RAII для мьютекса!
    {
        std::lock_guard<std::mutex> lock(mu);  // acquire
        // критичная секция
    }  // lock.~lock_guard() вызывается, мьютекс разблокируется
}

// Эквивалент БЕЗ RAII (manual):
void critical_section_bad() {
    mu.lock();
    try {
        // критичная секция
        // Если здесь исключение — unlock не вызовется!
    } catch (...) {
        mu.unlock();
        throw;
    }
    mu.unlock();
}

2. std::unique_ptr

void foo() {
    auto ptr = std::make_unique<MyClass>();  // acquire
    ptr->do_work();
    
    // Если исключение? Деструктор уникального указателя удаляет объект
}  // delete автоматически

3. std::string

void process() {
    std::string data = "hello";  // RAII: выделяет память
    
    // Исключение? Строка всё равно очистится (деструктор вызовется)
    if (error) throw std::exception();
    
}  // data.~string() освобождает память

Правило нуля и правило пяти

Правило нуля (Rule of Zero)

Если класс использует только RAII типы, не пиши деструктор, копирование, перемещение.

// ✅ ХОРОШО: Rule of Zero
class DataProcessor {
    std::unique_ptr<Buffer> buffer;    // RAII
    std::vector<std::string> log;      // RAII
    std::lock_guard<std::mutex> lock;  // RAII
    
    // Дефолтные конструктор/деструктор/copy/move
    // Компилятор сгенерирует правильно!
};

Правило пяти (Rule of Five)

Если пишешь деструктор, пиши и копирование + перемещение.

// ❌ ОПАСНО: Rule of Five violation
class CustomBuffer {
    char* data;
    size_t size;
    
public:
    CustomBuffer(size_t sz) : size(sz) {
        data = new char[sz];  // manual allocation
    }
    
    ~CustomBuffer() {
        delete[] data;  // manual deallocation
    }
    
    // ЗАБЫЛИ copy constructor + assignment!
    // Компилятор генерирует default: shallow copy → double delete!
};

// ✅ ПРАВИЛЬНО: Rule of Five
class CustomBuffer {
    char* data;
    size_t size;
    
public:
    // 1. Constructor
    CustomBuffer(size_t sz) : size(sz), data(new char[sz]) {}
    
    // 2. Destructor
    ~CustomBuffer() { delete[] data; }
    
    // 3. Copy constructor
    CustomBuffer(const CustomBuffer& other) 
        : size(other.size), data(new char[other.size]) {
        std::copy(other.data, other.data + size, data);
    }
    
    // 4. Copy assignment
    CustomBuffer& operator=(const CustomBuffer& other) {
        if (this != &other) {
            delete[] data;  // старые данные
            size = other.size;
            data = new char[other.size];
            std::copy(other.data, other.data + size, data);
        }
        return *this;
    }
    
    // 5. Move constructor
    CustomBuffer(CustomBuffer&& other) noexcept 
        : size(other.size), data(other.data) {
        other.data = nullptr;
        other.size = 0;
    }
    
    // 6. Move assignment
    CustomBuffer& operator=(CustomBuffer&& other) noexcept {
        delete[] data;
        size = other.size;
        data = other.data;
        other.data = nullptr;
        return *this;
    }
};

RAII в многопоточности

class TransactionGuard {
    Database& db;
    TransactionId tx_id;
    
public:
    TransactionGuard(Database& d) : db(d) {
        tx_id = db.begin_transaction();  // acquire
    }
    
    ~TransactionGuard() {
        // Если исключение — автоматический rollback!
        if (std::uncaught_exceptions() > 0) {
            db.rollback(tx_id);
        } else {
            db.commit(tx_id);
        }
    }
};

void transfer_money(int from_id, int to_id, int amount) {
    TransactionGuard tx(database);
    
    Account& from = db.get_account(from_id);
    Account& to = db.get_account(to_id);
    
    if (from.balance < amount) {
        throw std::invalid_argument("Insufficient funds");
        // Автоматический rollback благодаря RAII!
    }
    
    from.balance -= amount;
    to.balance += amount;
    
}  // tx.~TransactionGuard() вызовется, commit() будет выполнен

RAII в network programming

class TcpConnection {
    int socket_fd;
    
public:
    TcpConnection(const std::string& host, int port) {
        socket_fd = ::socket(AF_INET, SOCK_STREAM, 0);
        if (socket_fd < 0) throw std::runtime_error("socket failed");
        
        struct sockaddr_in addr = {};
        // ... setup addr
        
        if (::connect(socket_fd, ...) < 0) {
            ::close(socket_fd);
            throw std::runtime_error("connect failed");
        }
    }
    
    ~TcpConnection() {
        if (socket_fd >= 0) {
            ::close(socket_fd);
        }
    }
    
    TcpConnection(const TcpConnection&) = delete;
    TcpConnection& operator=(const TcpConnection&) = delete;
};

void send_request() {
    TcpConnection conn("api.example.com", 443);
    
    // Если исключение — сокет закроется автоматически
    conn.send_data(request);
    auto response = conn.receive_data();
    
}  // сокет закроется гарантированно

Преимущества RAII

  1. Exception safety — ресурсы освобождаются даже при исключениях
  2. No memory leaks — невозможны утечки (за редкими исключениями)
  3. Clear ownership — видно, кто владит ресурсом
  4. Performance — нет overhead, управление во время компиляции
  5. Readability — меньше boilerplate, больше смысла

Резюме

RAII = ресурсы привязаны к объектам, которые их держат.

  • Захват в конструкторе, освобождение в деструкторе
  • Автоматически работает при исключениях
  • Используй unique_ptr вместо new/delete
  • Используй std::lock_guard вместо manual lock/unlock
  • Это не просто паттерн — это философия безопасного C++

RAII — одна из причин, почему современный C++ считается более безопасным, чем Java или Python (по управлению ресурсами) — нет сборщика мусора, нет задержек, нет сюрпризов.