Что такое exception_ptr?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Что такое 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 где было выброшено.
Ключевые особенности
-
null exception_ptr — означает что исключения не было
if(eptr) { /* есть исключение */ } -
Копируемость — можно передать в другой поток
std::exception_ptr e1 = ...; std::exception_ptr e2 = e1; // Оба указывают на одно исключение -
Type erasure — точный тип исключения теряется при сохранении
// Сохранили std::runtime_error // При rethrow нужно ловить catch(...) или правильный тип -
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 — мощный инструмент для создания надёжных многопоточных систем.