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

Какие интерфейсы используют RAII?

2.3 Middle🔥 151 комментариев
#Язык C++

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

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

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

Какие интерфейсы используют RAII

RAII (Resource Acquisition Is Initialization) — это идиома C++, где управление ресурсами привязано к жизненному циклу объекта. Ресурсы захватываются в конструкторе и освобождаются в деструкторе. Множество интерфейсов в C++ используют этот принцип.

1. Умные указатели (Smart Pointers)

unique_ptr

class File {
public:
    File(const string& name) {
        handle = open(name.c_str(), O_RDONLY);
        if (handle < 0) throw runtime_error("Cannot open");
    }
    ~File() {
        if (handle >= 0) close(handle);
    }
private:
    int handle;
};

// RAII: файл закроется автоматически
{
    unique_ptr<File> file = make_unique<File>("data.txt");
    file->read();
}  // Деструктор вызывается, файл закрывается

shared_ptr

class Database {
public:
    Database(const string& connection_string) {
        conn = dbConnect(connection_string);
    }
    ~Database() {
        if (conn) dbDisconnect(conn);
    }
private:
    DBConnection* conn;
};

// RAII с подсчётом ссылок
{
    shared_ptr<Database> db1 = make_shared<Database>("host=localhost");
    {
        shared_ptr<Database> db2 = db1;  // ref_count = 2
    }  // db2 уничтожена, ref_count = 1
}  // db1 уничтожена, ref_count = 0, вызывается деструктор

2. Потоки и синхронизация

std::thread

void worker() {
    cout << "Working..." << endl;
}

{
    thread t(worker);
    // ...делаем что-то...
}  // ОШИБКА: не присоединился!

// Правильно с RAII:
{
    thread t(worker);
    // ...делаем что-то...
    t.join();  // Присоединяемся
}  // Деструктор не вызывает terminate()

std::lock_guard и std::unique_lock

class ThreadSafeCounter {
private:
    mutable mutex mut;
    int counter = 0;
    
public:
    void increment() {
        lock_guard<mutex> lock(mut);  // Захват в конструкторе
        counter++;
    }  // Освобождение в деструкторе
    
    int value() const {
        unique_lock<mutex> lock(mut);
        return counter;
    }  // Освобождение в деструкторе
};

// Безопасна даже при исключении
void example() {
    ThreadSafeCounter counter;
    try {
        counter.increment();
        throw runtime_error("Ошибка!");
    } catch (...) {
        // Мьютекс уже освобождён благодаря RAII
    }
}

3. Файловые потоки

std::ifstream и std::ofstream

{
    ifstream input("data.txt");
    if (!input) throw runtime_error("Cannot open");
    
    string line;
    while (getline(input, line)) {
        cout << line << endl;
    }
}  // Файл закроется в деструкторе

// Безопасна при исключении
void readFile(const string& filename) {
    ifstream file(filename);
    if (!file.is_open()) {
        throw runtime_error("Cannot open");
    }
    
    string line;
    while (getline(file, line)) {
        if (line.empty()) {
            throw runtime_error("Empty line");
        }
        process(line);
    }
}  // Файл закроется даже при исключении

4. Контейнеры STL

std::vector, std::map, std::string

{
    vector<int> vec;
    vec.reserve(1000);  // Выделение памяти
    
    for (int i = 0; i < 100; i++) {
        vec.push_back(i);
    }
    
    // Используем вектор
}  // Вся память освобождается в деструкторе

// Безопасна при исключении
class DataProcessor {
private:
    map<string, vector<int>> data;
    
public:
    void process() {
        data["key1"].push_back(1);
        if (someCondition()) {
            throw runtime_error("Error");
        }
        data["key2"].push_back(2);
    }
}  // Все контейнеры очищаются автоматически

5. Таймеры и async операции

std::async

future<int> calculateAsync() {
    return async(launch::async, []() {
        // Вычисления в отдельном потоке
        return 42;
    });
}

{
    future<int> result = calculateAsync();
    int value = result.get();  // Ждём результата
}  // Поток завершится, ресурсы освобождены

6. Custom RAII классы

Пример: Connection pool

class Connection {
public:
    Connection(ConnectionPool& pool, const string& addr)
        : pool_(pool), handle_(connect(addr)) {
        if (!handle_) throw runtime_error("Cannot connect");
    }
    
    ~Connection() {
        disconnect(handle_);
    }
    
    void execute(const string& query) {
        if (!handle_) throw runtime_error("Not connected");
        runQuery(handle_, query);
    }
    
private:
    ConnectionPool& pool_;
    ConnectionHandle handle_;
};

// Использование
{
    Connection conn(pool, "db.example.com");
    conn.execute("SELECT * FROM users");
}  // Соединение закроется, вернётся в пул

Пример: Локальное блокирование ресурса

class ResourceLock {
public:
    ResourceLock(Resource& res) : resource_(res) {
        resource_.lock();  // Захватываем
    }
    
    ~ResourceLock() {
        resource_.unlock();  // Отпускаем
    }
    
private:
    Resource& resource_;
};

void criticalSection(Resource& res) {
    ResourceLock lock(res);  // RAII: захват
    // Работаем с res
}  // RAII: освобождение

Пример: Временное изменение состояния

class State {
public:
    bool debug = false;
    
    class DebugMode {
    public:
        DebugMode(State& state) : state_(state), saved_(state.debug) {
            state_.debug = true;
        }
        
        ~DebugMode() {
            state_.debug = saved_;
        }
        
    private:
        State& state_;
        bool saved_;
    };
};

{
    State state;
    {
        State::DebugMode debug(state);
        // debug = true
    }
    // debug = false (восстановлено)
}

7. Транзакции в БД

class Transaction {
public:
    Transaction(Database& db) : db_(db) {
        db_.beginTransaction();
    }
    
    ~Transaction() {
        if (committed_) {
            db_.commit();
        } else {
            db_.rollback();
        }
    }
    
    void commit() {
        committed_ = true;
    }
    
private:
    Database& db_;
    bool committed_ = false;
};

// Безопасна при ошибках
{
    Transaction tx(db);
    
    try {
        db.execute("INSERT INTO users VALUES (1, 'John')");
        db.execute("INSERT INTO users VALUES (2, 'Jane')");
        tx.commit();
    } catch (...) {
        // Автоматический rollback в деструкторе
    }
}

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

  1. Автоматическое управление: Нет утечек ресурсов
  2. Exception safety: Деструктор вызывается даже при исключении
  3. Простота: Разработчик не думает об освобождении
  4. Scope-based: Ресурсы следуют области видимости переменной
  5. Zero-cost abstraction: Компилятор оптимизирует код

Anti-patterns (чего избегать)

// ПЛОХО: нарушение RAII
{
    FILE* file = fopen("data.txt", "r");
    if (!file) throw runtime_error("Cannot open");
    
    try {
        readFile(file);
    } catch (...) {
        fclose(file);  // Может не выполниться при вложенном исключении
        throw;
    }
    fclose(file);
}

// ХОРОШО: RAII
{
    unique_ptr<FILE, decltype(&fclose)> file(
        fopen("data.txt", "r"),
        &fclose
    );
    if (!file) throw runtime_error("Cannot open");
    readFile(file.get());
}  // Гарантированное закрытие

Заключение

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

Какие интерфейсы используют RAII? | PrepBro