Как сообщить другому потоку про брошенный exception?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Передача исключений между потоками в C++
Проблема с исключениями в многопоточности
Исключения в C++ локальны для потока, в котором они были выброшены. Если вы просто выбросите исключение в одном потоке, оно не будет перехвачено в другом потоке. Нужны специальные механизмы для передачи информации об исключении.
1. Использование std::exception_ptr (C++11)
Это самый стандартный и рекомендуемый способ. std::exception_ptr сохраняет указатель на исключение, которое можно позже пробросить в другом потоке:
#include <thread>
#include <exception>
#include <iostream>
std::exception_ptr exception = nullptr;
void worker() {
try {
// Код, который может выбросить исключение
throw std::runtime_error("Ошибка в потоке!");
} catch (...) {
// Сохраняем текущее исключение
exception = std::current_exception();
}
}
int main() {
std::thread t(worker);
t.join();
// Проверяем, было ли исключение
if (exception) {
try {
// Пробрасываем исключение в главном потоке
std::rethrow_exception(exception);
} catch (const std::exception& e) {
std::cerr << "Поймано исключение: " << e.what() << std::endl;
}
}
return 0;
}
2. Обёртка для std::thread
Для удобства можно создать класс-обёртку, которая автоматически сохраняет исключения:
#include <thread>
#include <exception>
#include <functional>
class ManagedThread {
private:
std::thread thread;
std::exception_ptr exception;
template<typename Func>
void run(Func func) {
try {
func();
} catch (...) {
exception = std::current_exception();
}
}
public:
template<typename Func>
ManagedThread(Func func) : thread(&ManagedThread::run<Func>, this, func) {}
void join() {
thread.join();
if (exception) {
std::rethrow_exception(exception);
}
}
};
int main() {
try {
ManagedThread t([]() {
throw std::logic_error("Ошибка потока");
});
t.join(); // Исключение пробросится сюда
} catch (const std::exception& e) {
std::cout << "Обработано: " << e.what() << std::endl;
}
return 0;
}
3. Использование std::promise и std::future
Это более гибкий подход для передачи результатов или исключений:
#include <thread>
#include <future>
#include <iostream>
int main() {
std::promise<int> promise;
auto future = promise.get_future();
std::thread t([&promise]() {
try {
throw std::runtime_error("Ошибка вычисления");
// promise.set_value(42); // Если успех
} catch (...) {
// Сохраняем исключение в promise
promise.set_exception(std::current_exception());
}
});
try {
// При получении результата исключение пробросится
int result = future.get();
std::cout << "Результат: " << result << std::endl;
} catch (const std::exception& e) {
std::cerr << "Исключение: " << e.what() << std::endl;
}
t.join();
return 0;
}
4. Использование std::async
Самый удобный способ — использовать std::async, который автоматически управляет исключениями:
#include <future>
#include <iostream>
int main() {
auto future = std::async(std::launch::async, []() -> int {
throw std::overflow_error("Переполнение");
return 42;
});
try {
// Исключение пробросится автоматически
int result = future.get();
std::cout << "Результат: " << result << std::endl;
} catch (const std::overflow_error& e) {
std::cerr << "Ошибка: " << e.what() << std::endl;
}
return 0;
}
5. Использование std::packaged_task
Для более сложных случаев:
#include <thread>
#include <future>
int main() {
std::packaged_task<int()> task([]() -> int {
throw std::bad_alloc();
return 100;
});
auto future = task.get_future();
std::thread t(std::move(task));
try {
future.get();
} catch (const std::bad_alloc& e) {
std::cout << "Нет памяти!" << std::endl;
}
t.join();
return 0;
}
6. Логирование и обработка с std::exception_ptr
Для систем с несколькими потоками полезна централизованная обработка:
#include <mutex>
#include <vector>
#include <exception>
class ExceptionHandler {
private:
std::mutex mutex;
std::vector<std::exception_ptr> exceptions;
public:
void capture() {
std::lock_guard<std::mutex> lock(mutex);
exceptions.push_back(std::current_exception());
}
void rethrow_all() {
for (auto& exc : exceptions) {
try {
std::rethrow_exception(exc);
} catch (const std::exception& e) {
std::cerr << "Ошибка потока: " << e.what() << std::endl;
}
}
exceptions.clear();
}
};
Сравнение методов
| Метод | Преимущества | Недостатки |
|---|---|---|
| exception_ptr | Стандартный, простой | Нужно вручную управлять |
| promise/future | Гибкий, универсальный | Чуть сложнее |
| async | Самый удобный | Меньше контроля над потоком |
| packaged_task | Точный контроль | Больше кода |
Ключевые моменты
- Исключения не пересекают границы потоков автоматически
- std::exception_ptr — сохранить исключение для позднего пробрасывания
- std::current_exception() — получить текущее исключение в блоке catch
- std::rethrow_exception() — пробросить сохранённое исключение
- std::async/promise — предпочтительные современные подходы в C++11+