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

Что такое exception_ptr?

1.7 Middle🔥 141 комментариев
#Исключения и обработка ошибок

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

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

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

Что такое exception_ptr?

exception_ptr — это указатель на исключение в C++ (из <exception>), который позволяет захватить, сохранить и передать исключение между потоками. Это продвинутая функция для асинхронного обработки ошибок.

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

Проблема:

std::thread t([](){ 
    throw std::runtime_error("Ошибка в потоке!");
});
t.join();  // Программа кушается, невозможно обработать исключение

Исключение в потоке не может пересечь границу потока и попасть в основной поток. exception_ptr решает эту проблему.

Что это дает?

#include <exception>

std::exception_ptr eptr;

try {
    // Что-то опасное
    throw std::runtime_error("Ошибка!");
} catch(...) {
    // Сохраняем указатель на исключение
    eptr = std::current_exception();
}

// Потом, где-то в другом месте кода
if(eptr) {
    try {
        std::rethrow_exception(eptr);  // Пробрасываем сохранённое исключение
    } catch(const std::runtime_error& e) {
        std::cout << "Поймано: " << e.what() << std::endl;
    }
}

Использование с потоками

Типичный паттерн — обработка ошибок в рабочем потоке:

#include <thread>
#include <exception>
#include <iostream>

class ThreadWorker {
public:
    void run() {
        try {
            doWork();
        } catch(...) {
            exception = std::current_exception();
        }
    }
    
    void checkException() {
        if(exception) {
            std::rethrow_exception(exception);
        }
    }

private:
    std::exception_ptr exception;
    
    void doWork() {
        // Какая-то работа
        throw std::logic_error("Что-то пошло не так");
    }
};

int main() {
    ThreadWorker worker;
    std::thread t(&ThreadWorker::run, &worker);
    t.join();
    
    try {
        worker.checkException();  // Выбросит исключение если было
    } catch(const std::logic_error& e) {
        std::cerr << "Error: " << e.what() << std::endl;
    }
    return 0;
}

Практический пример: параллельная обработка

#include <thread>
#include <vector>
#include <exception>

void processItem(int item, std::vector<std::exception_ptr>& errors) {
    try {
        if(item < 0) {
            throw std::invalid_argument("Negative item");
        }
        // Обработка item
        std::cout << "Processing " << item << std::endl;
    } catch(...) {
        errors.push_back(std::current_exception());
    }
}

int main() {
    std::vector<std::exception_ptr> errors;
    std::vector<std::thread> threads;
    std::vector<int> data = {1, 2, -3, 4, -5};
    
    // Запускаем работу в потоках
    for(int item : data) {
        threads.emplace_back(processItem, item, std::ref(errors));
    }
    
    // Ждём все потоки
    for(auto& t : threads) {
        t.join();
    }
    
    // Обрабатываем ошибки
    for(auto& eptr : errors) {
        try {
            std::rethrow_exception(eptr);
        } catch(const std::invalid_argument& e) {
            std::cerr << "Invalid: " << e.what() << std::endl;
        } catch(const std::exception& e) {
            std::cerr << "Error: " << e.what() << std::endl;
        }
    }
    
    return 0;
}

С async и future

exception_ptr особенно полезна с std::async:

#include <future>

int riskyFunction() {
    throw std::runtime_error("Async error!");
    return 42;
}

int main() {
    auto future = std::async(riskyFunction);
    
    try {
        int result = future.get();  // Вызовет исключение из потока
        std::cout << "Result: " << result << std::endl;
    } catch(const std::runtime_error& e) {
        std::cout << "Caught async error: " << e.what() << std::endl;
    }
    
    return 0;
}

future::get() автоматически использует rethrow_exception внутри.

Как это работает внутри?

Упрощённая реализация:

class exception_ptr {
private:
    std::shared_ptr<std::exception> ptr;  // Указатель на исключение
    
public:
    exception_ptr() = default;
    
    // current_exception() копирует исключение в heap
    friend exception_ptr current_exception() noexcept {
        exception_ptr e;
        try {
            throw;  // Пробросит текущее исключение
        } catch(...) {
            e.ptr = std::make_shared<std::exception_ptr>(...);
        }
        return e;
    }
    
    // rethrow_exception достаёт исключение и пробросит его
    friend void rethrow_exception(exception_ptr p) {
        if(p.ptr) {
            throw *p.ptr;  // Пробросит сохранённое исключение
        }
    }
};

Важно: исключение копируется на heap, поэтому оно живёт дольше scope где было выброшено.

Ключевые особенности

  1. null exception_ptr — означает что исключения не было

    if(eptr) { /* есть исключение */ }
    
  2. Копируемость — можно передать в другой поток

    std::exception_ptr e1 = ...;
    std::exception_ptr e2 = e1;  // Оба указывают на одно исключение
    
  3. Type erasure — точный тип исключения теряется при сохранении

    // Сохранили std::runtime_error
    // При rethrow нужно ловить catch(...) или правильный тип
    
  4. Performance — создание/копирование имеет затраты

    // Медленно: выделение памяти, копирование
    // Используй когда исключения редки
    

Где это реально используется?

1. Потокобезопасные очереди с обработкой ошибок:

class SafeQueue {
private:
    std::vector<std::exception_ptr> exceptions;
    
public:
    void processAsync(std::function<void()> task) {
        std::thread t([this, task]() {
            try { task(); }
            catch(...) { exceptions.push_back(std::current_exception()); }
        });
        t.detach();
    }
};

2. Распределённые вычисления:

// Отправляем результат вычисления или исключение через сеть
struct ComputeResult {
    std::exception_ptr error;
    int result;
};

3. Promise/Future паттерны:

std::promise<int> promise;
try {
    promise.set_value(complexCalculation());
} catch(...) {
    promise.set_exception(std::current_exception());
}

Когда НЕ использовать

  • Просто обработка исключений в одном потоке (используй try/catch)
  • Если знаешь точный тип исключения (передай результат вместо exception_ptr)
  • Критичная по производительности код (копирование исключений дорого)

Итоговый паттерн для production

template<typename T>
struct Result {
    T value;
    std::exception_ptr error;
    
    T getOrThrow() const {
        if(error) std::rethrow_exception(error);
        return value;
    }
};

std::future<Result<int>> asyncCompute() {
    return std::async([](){ 
        Result<int> r;
        try {
            r.value = 42;  // вычисления
        } catch(...) {
            r.error = std::current_exception();
        }
        return r;
    });
}

exception_ptr — мощный инструмент для создания надёжных многопоточных систем.