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

Как сообщить другому потоку про брошенный exception?

2.0 Middle🔥 51 комментариев
#Многопоточность и синхронизация#Исключения и обработка ошибок

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

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

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

Передача исключений между потоками в 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+