Как можно проверить несколько потоков на наличие исключений в C++?

Ответ

Самый удобный способ — использовать std::future, которая автоматически захватывает исключения, брошенные в асинхронной задаче. При вызове get() исключение пробрасывается в вызывающий поток.

Пример с std::async и сбором исключений:

#include <future>
#include <vector>
#include <iostream>
#include <stdexcept>

int risky_operation(int id) {
    if (id % 3 == 0) {
        throw std::runtime_error("Bad luck from task " + std::to_string(id));
    }
    return id * 100;
}

int main() {
    std::vector<std::future<int>> futures;
    // Запускаем несколько асинхронных задач
    for (int i = 0; i < 10; ++i) {
        futures.emplace_back(std::async(std::launch::async, risky_operation, i));
    }

    std::vector<std::exception_ptr> exceptions;
    std::vector<int> results;
    // Собираем результаты и исключения
    for (auto& fut : futures) {
        try {
            results.push_back(fut.get()); // get() пробросит исключение, если оно было
        } catch (...) {
            exceptions.push_back(std::current_exception()); // Сохраняем исключение для последующей обработки
        }
    }

    // Обрабатываем все собранные исключения
    for (const auto& eptr : exceptions) {
        try {
            if (eptr) std::rethrow_exception(eptr);
        } catch (const std::exception& e) {
            std::cerr << "Caught exception: " << e.what() << 'n';
        }
    }
    return 0;
}

Альтернатива для ручного управления потоками: Можно хранить std::exception_ptr в разделяемой переменной, защищенной мьютексом, и каждый поток в блоке catch(...) должен сохранять туда std::current_exception().

Ответ 18+ 🔞

Давай разберём эту тему, а то у меня уже волнение ебать, как люди с исключениями в потоках косячат. Представь: запустил ты кучу задач, они там втихую падают, а ты сидишь и ждёшь результата, как дурак. А потом — бац! — программа накрылась медным тазом, и непонятно, где и что сломалось.

Самый адекватный способ, чтобы не облажаться — это std::future. Она, сука, умная: если в асинхронной задаче вылетело исключение, она его ловит и прячет у себя. А когда ты вызовешь get(), она тебе это исключение аккуратненько в морду выбросит. Красота, да?

Вот смотри, как это выглядит на практике. Берём std::async, запускаем кучу задач, некоторые из них специально падают — чисто для прикола.

#include <future>
#include <vector>
#include <iostream>
#include <stdexcept>

int risky_operation(int id) {
    if (id % 3 == 0) {
        throw std::runtime_error("Bad luck from task " + std::to_string(id));
    }
    return id * 100;
}

int main() {
    std::vector<std::future<int>> futures;
    // Запускаем несколько асинхронных задач
    for (int i = 0; i < 10; ++i) {
        futures.emplace_back(std::async(std::launch::async, risky_operation, i));
    }

    std::vector<std::exception_ptr> exceptions;
    std::vector<int> results;
    // Собираем результаты и исключения
    for (auto& fut : futures) {
        try {
            results.push_back(fut.get()); // get() пробросит исключение, если оно было
        } catch (...) {
            exceptions.push_back(std::current_exception()); // Сохраняем исключение для последующей обработки
        }
    }

    // Обрабатываем все собранные исключения
    for (const auto& eptr : exceptions) {
        try {
            if (eptr) std::rethrow_exception(eptr);
        } catch (const std::exception& e) {
            std::cerr << "Caught exception: " << e.what() << 'n';
        }
    }
    return 0;
}

Суть в чём: мы не пытаемся ловить исключения прямо в рабочем потоке. Мы даём им спокойно вылететь, а future их для нас консервирует. Потом, в основном потоке, когда мы начинаем собирать урожай через get(), мы уже готовы к подвоху. Поймали исключение — сохранили в std::exception_ptr. Это такая умная хуйня, которая может переносить исключение куда угодно. А обработали мы их потом, все разом, чтобы в лог красиво всё вывести.

А если тебе std::async не нравится, и ты сам потоки крутишь, как гонщик? Тогда тебе придётся повозиться. Нужна общая переменная, защищённая мьютексом, куда каждый поток будет складывать своё std::exception_ptr, если его накроет. И в блоке catch(...) каждый твой воркер должен делать std::current_exception() и пихать результат в эту общую копилку. А главный поток потом всё это разгребает. Но это уже, блядь, ручная работа, как будто на дворе 2002-й год. std::async и future — это цивилизованно.