exception_ptr

1. std::exception_ptr 是什么?

std::exception_ptr 是 C++ 标准库中的一个类型,它类似于一个智能指针,专门用来指向(保存)一个异常对象

它的主要作用是在不同的上下文(尤其是不同的线程)之间传递异常

2. 为什么要使用它?

在标准的 C++ try-catch 块中,异常通常只能在同一个线程内被捕获。

  • 问题: 如果你在一个子线程(Worker Thread)中抛出了异常,而没有在那个线程内部捕获它,程序会直接崩溃(调用 std::terminate)。你不能直接把异常“抛”到主线程去。
  • 解决方案: std::exception_ptr 允许你在子线程中捕获异常,把它保存到一个变量里,然后把这个变量传递给主线程。主线程拿到后,可以把这个异常重新抛出(Rethrow)并进行处理。

3. 核心函数

使用 exception_ptr 通常涉及以下三个函数:

  1. std::current_exception():
    • 只能在 catch 块内部使用。
    • 它会捕获当前正在处理的异常,并返回一个指向该异常的 std::exception_ptr 对象。
    • 即使你不知道异常的具体类型(例如在 catch(...) 中),它也能工作。
  2. std::rethrow_exception(ptr):
    • 接受一个 std::exception_ptr 参数。
    • 它会重新抛出该指针所指向的异常对象,就像刚刚发生一样。这通常在主线程中调用。
  3. std::make_exception_ptr(e):
    • 创建一个指向异常对象 e 的指针,而不需要先抛出它。

4. 代码示例

这是一个经典的跨线程异常处理示例:

C++

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

// 定义一个全局变量来存储异常指针(实际开发中通常通过参数传递)
std::exception_ptr globalExceptionPtr = nullptr;

void workerThread() {
    try {
        // 模拟子线程发生错误
        throw std::runtime_error("子线程发生了错误!");
    } catch (...) {
        // 1. 捕获当前异常,并将其保存到指针中
        // 注意:这里我们不需要知道异常的具体类型
        globalExceptionPtr = std::current_exception();
    }
}

int main() {
    std::thread t(workerThread);
    t.join(); // 等待子线程结束

    // 检查子线程是否传回了异常
    if (globalExceptionPtr) {
        try {
            // 2. 在主线程中重新抛出这个异常
            std::rethrow_exception(globalExceptionPtr);
        } catch (const std::exception& e) {
            // 3. 在这里统一处理异常
            std::cout << "主线程捕获到: " << e.what() << std::endl;
        }
    }

    return 0;
}

5. 这种模式的关键点:生命周期管理

您可能会担心:“子线程结束了,栈内存释放了,那个异常对象还在吗?”

  • 答案是肯定的。 std::exception_ptr 的行为类似于 std::shared_ptr(共享指针)。
  • 当您调用 std::current_exception() 时,它会延长异常对象的生命周期。
  • 即使 workerThread 结束,只要 globalExceptionPtr 还在引用它,那个异常对象就会一直存在堆内存中,直到主线程处理完毕并销毁指针。

6. 改进建议:避免全局变量

在您的示例代码中使用了全局变量 globalExceptionPtr。在实际的多线程并发场景中,全局变量是不安全的(如果启动了两个线程,它们会覆盖同一个变量)。

改进写法(通过引用传递): 我们可以将 exception_ptr 作为参数传给线程:

void workerThread(std::exception_ptr& outputPtr) { // 接收引用
    try {
        throw std::runtime_error("子线程错误");
    } catch (...) {
        outputPtr = std::current_exception(); // 写入传入的变量
    }
}

int main() {
    std::exception_ptr ptr = nullptr;
    std::thread t(workerThread, std::ref(ptr)); // 使用 std::ref 传递引用
    t.join();
    // ... 后续处理同上
}

3. 现代 C++ 的“自动挡”写法:std::future

虽然手动使用 exception_ptr 很灵活,但 C++11 提供了更高层的抽象 std::promisestd::future,它们内部封装了您刚才写的这套逻辑

使用 std::future,您不需要手动定义 exception_ptr 变量,也不需要手动 rethrow,系统会自动帮您完成:

对比代码(使用 Future):

#include <iostream>
#include <thread>
#include <future> // 引入 future 头文件

void workerThread(std::promise<void> prom) {
    try {
        // 模拟做一些工作...
        throw std::runtime_error("Future 里的错误!");
    } catch (...) {
        // 【关键点】自动捕获并存入 promise,不需要我们手动管理指针
        prom.set_exception(std::current_exception()); 
    }
}

int main() {
    std::promise<void> prom;
    std::future<void> fut = prom.get_future();

    std::thread t(workerThread, std::move(prom));

    try {
        // 当我们调用 get() 获取结果时
        // 如果线程里有异常,这里会自动抛出 (Rethrow)
        fut.get(); 
    } catch (const std::exception& e) {
        std::cout << "主线程自动捕获: " << e.what() << std::endl;
    }

    t.join();
    return 0;
}

总结

current_exception + exception_ptr + rethrow_exception 是 C++ 异常传递的核心铁三角

  • 手动模式:适合编写底层库、线程池任务调度器。
  • 自动模式std::future):适合日常业务开发,代码更简洁。
posted @ 2025-12-20 21:23  belief73  阅读(0)  评论(0)    收藏  举报