1 C++抛出异常与捕获异常

在C++中我们经常使用以下代码抛出异常和捕获异常

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

int main()
{
    try
    {
        throw std::runtime_error("Exception: runtime error in thread");
    }
    catch (const std::exception& ex)
    {
        std::cout << "Exited with exception: " << ex.what() << "\n";
    }

    return 0;
}

上述抛出代码和捕获代码都是在主线程中进行了,我们能够顺利的捕捉到抛出的异常。

那如果我在子线程中抛出异常,在主线程中能捕捉到吗?

C++主线程无法捕获子线程异常!!!

C++在子线程中抛出异常是无法在主线程捕获到的,看以下示例代码

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

void StartThread()
{
    throw std::runtime_error("Exception: runtime error in thread");
}

int main()
{
    try
    {
        std::thread thread(StartThread);
        thread.join();
    }
    catch (const std::exception& ex)
    {
        std::cout << "Thread exited with exception: " << ex.what() << "\n";
    }

    return 0;
}

执行程序,没有输出任何内容,这说明C++中子线程中抛出的异常无法在主线程中捕获,也就是说一个线程中抛出的异常只能在同一线程捕获。

2 C++如何在主线程中捕获子线程抛出的异常

上面我们了解了在C++中不能在主线程中捕捉到子线程抛出的异常,那么如果业务强制要求需要在主线程中捕获子线程的异常,那么我们该如何实现呢?

理想的一个设计是:

  • 在主线程中声明一个变量,将该变量传递到子线程中
  • 在子线程抛出异常的时候,修改变量的值为异常值
  • 然后在主线程中捕获异常

基于上面的设计,我们使用std::promise来完成这一实现,先看代码

#include <iostream>
#include <thread>
#include <exception>
#include <stdexcept>
#include <future>

void StartThread(std::promise<void>& promise)
{
    try {
        std::this_thread::sleep_for(std::chrono::seconds(5));
        throw std::runtime_error("Exception: runtime error in thread");
        promise.set_value();
    }
    catch (...)
    {
        promise.set_exception(std::current_exception());
    }
}

int main()
{
    std::promise<void> exception_promise;
    std::thread thread(std::bind(StartThread, std::ref(exception_promise)));

    std::future<void> exception_future = exception_promise.get_future();

    // 等待子线程执行完成
    for (int i = 0; ; i++)
    {
        if (exception_future.wait_for(std::chrono::seconds(1)) != std::future_status::timeout)
            break;
        std::cout << "waiting ... [" << i << "] seconds" << std::endl;
    }

    try
    {
        exception_future.get();
    }
    catch (const std::exception& e)
    {
        std::cout << "Thread exited with exception: " << e.what() << "\n";
    }

    thread.join();

    return 0;
}

上述代码成功在主线程中捕获到了子线程抛出的异常,运行日志如下

waiting ... [0] seconds
waiting ... [1] seconds
waiting ... [2] seconds
waiting ... [3] seconds
Thread exited with exception: Exception: runtime error in thread

在上述代码中,我们在主线程中声明了一个std::promiss<void>类型的变量,并传递到子线程中,然后在子线程中使用try catch语句捕获异常然后设置给std::promise<void>变量;而在主线程中我们使用std::future获取到std::promise的future,然后使用try catch成功捕捉到设置到std::promise<void>变量中的异常。