C++中如何处理线程函数抛出的异常

两种处理方法

在C++中,如果你在一个通过std::thread创建的线程中抛出了一个异常,并且没有在该线程中捕获它,那么当线程结束时,异常会被传递到包含线程。但是,直接从std::thread对象中获取这个异常并不是直截了当的。

从C++17开始,你可以使用std::thread::join()来等待线程完成,并且如果线程中有未捕获的异常,则会传播到调用join()的代码中。这意味着如果你的线程抛出了一个异常并且你尝试调用join(),那么这个异常会在主线程中被捕获。

以下是一个简单的例子:

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

void throwExceptionInThread()
{
    throw std::runtime_error("Exception from thread");
}

int main()
{
    std::thread t(throwExceptionInThread);
    try {
        t.join(); // 如果线程中有异常抛出,这里会传播出来
    } catch (const std::exception& e) {
        std::cerr << "Caught exception: " << e.what() << '\n';
    }
    return 0;
}

在这个例子中,如果throwExceptionInThread函数抛出一个异常,那么这个异常会在main函数中被捕获并处理。

对于C++11和C++14,你也可以这样做,但是如果没有显式地捕获异常,那么这个异常就会丢失,因为std::thread对象一旦销毁,异常信息就会消失。因此,在这些标准版本中,确保你的线程逻辑总是捕获任何可能抛出的异常,或者确保你在std::thread对象销毁之前捕获这些异常。

另外一种方法是手动管理异常,比如通过使用共享状态来传递异常信息:

#include <iostream>
#include <thread>
#include <exception>
#include <shared_ptr>

class ExceptionStorage {
public:
    void setException(std::exception_ptr ep) { m_exception = ep; }
    std::exception_ptr getException() { return m_exception; }

private:
    std::exception_ptr m_exception;
};

void throwExceptionInThread(std::shared_ptr<ExceptionStorage> storage)
{
    try {
        // 模拟线程工作
        throw std::runtime_error("Exception from thread");
    } catch (...) {
        // 将异常存储在共享状态中
        storage->setException(std::current_exception());
    }
}

int main()
{
    auto storage = std::make_shared<ExceptionStorage>();
    std::thread t(throwExceptionInThread, storage);
    t.join(); // 等待线程结束

    std::exception_ptr ep = storage->getException();
    if (ep) {
        try {
            std::rethrow_exception(ep);
        } catch (const std::exception& e) {
            std::cerr << "Caught exception: " << e.what() << '\n';
        }
    }
    return 0;
}

这种方法可以确保即使在线程结束之后,异常仍然可以被捕获和处理。

案例展示

让我们来看一个具体的示例,其中我们将在一个线程中执行一些任务,并处理可能发生的异常。我们将创建一个类来存储异常,并将此类的实例作为参数传递给线程。这样,如果线程中的操作抛出异常,我们可以将异常保存在共享状态中,并在主线程中重新抛出。

假设我们需要在一个独立的线程中执行一些文件处理任务,比如读取文件并将内容转换为大写。为了演示异常处理机制,我们故意让程序在某些条件下抛出异常。

以下是完整的示例代码:

#include <iostream>
#include <thread>
#include <exception>
#include <memory>
#include <fstream>
#include <string>

// 用于在线程间传递异常的类
class ExceptionStorage {
public:
    void setException(std::exception_ptr ep) { m_exception = ep; }
    std::exception_ptr getException() const { return m_exception; }

private:
    std::exception_ptr m_exception;
};

// 在线程中运行的任务
void processFile(const std::string& filename, std::shared_ptr<ExceptionStorage> storage)
{
    std::ifstream file(filename);
    if (!file.is_open()) {
        // 模拟文件打开失败的情况
        storage->setException(std::current_exception());
        return;
    }

    std::string content((std::istreambuf_iterator<char>(file)),
                         std::istreambuf_iterator<char>());
    // 转换内容为大写
    for (char& c : content) {
        c = std::toupper(c);
    }
    // 假设这里输出转换后的内容
    std::cout << content << std::endl;
}

int main()
{
    auto storage = std::make_shared<ExceptionStorage>();
    std::thread t(processFile, "nonexistentfile.txt", storage);

    // 等待线程结束
    t.join();

    std::exception_ptr ep = storage->getException();
    if (ep) {
        try {
            std::rethrow_exception(ep);
        } catch (const std::exception& e) {
            std::cerr << "Caught exception: " << e.what() << '\n';
        }
    }

    return 0;
}

在这个示例中,我们试图打开一个名为 nonexistentfile.txt 的文件,但实际上这个文件不存在。因此,当我们尝试打开文件时,std::ifstream 构造函数会抛出一个 std::runtime_error 异常(或类似异常)。这个异常被捕获,并通过 std::current_exception() 函数捕获当前的异常指针,然后将其存储在 ExceptionStorage 对象中。

当主线程调用 t.join() 并等待子线程完成后,它可以检查 ExceptionStorage 对象中的异常指针。如果存在异常指针,它会重新抛出异常,从而允许主线程处理这个异常。如果文件存在并且可以成功读取,那么不会有任何异常被存储,程序将正常运行。

案例扩展

我们可以在上面的基础上进一步扩展,以确保我们的代码更加健壮,并处理各种可能的情况。例如,我们可以添加更多的错误处理逻辑,以及提供更详细的错误信息。此外,还可以改进代码结构,使其更具模块化和可重用性。

下面是一个更详细的示例,包括错误处理和一些额外的功能:

  1. 增强错误处理:不仅处理文件不存在的情况,还处理其他潜在的错误,如权限问题等。
  2. 日志记录:增加日志记录功能,以便更好地跟踪错误和调试信息。
  3. 分离功能:将文件处理逻辑分离到一个单独的函数中,以提高代码的可读性和可维护性。

下面是改进后的代码:

#include <iostream>
#include <thread>
#include <exception>
#include <memory>
#include <fstream>
#include <string>
#include <sstream>

// 用于在线程间传递异常的类
class ExceptionStorage {
public:
    void setException(std::exception_ptr ep) { m_exception = ep; }
    std::exception_ptr getException() const { return m_exception; }

private:
    std::exception_ptr m_exception;
};

// 用于记录日志的辅助函数
void logError(const std::string& message) {
    std::cerr << "[ERROR] " << message << std::endl;
}

// 文件处理逻辑
void convertFileToUppercase(const std::string& filename, std::shared_ptr<ExceptionStorage> storage)
{
    std::ifstream file(filename);
    if (!file.is_open()) {
        // 捕获文件打开失败的情况
        try {
            std::ifstream file(filename); // 重新尝试一次,以确保捕获异常
        } catch (const std::exception& e) {
            logError("Failed to open file: " + filename);
            storage->setException(std::current_exception());
            return;
        }
    }

    std::stringstream buffer;
    buffer << file.rdbuf();
    std::string content = buffer.str();
    
    // 转换内容为大写
    for (char& c : content) {
        c = std::toupper(c);
    }

    // 输出转换后的内容
    std::cout << "Converted content: " << content << std::endl;
}

int main()
{
    auto storage = std::make_shared<ExceptionStorage>();
    std::thread t(convertFileToUppercase, "nonexistentfile.txt", storage);

    // 等待线程结束
    t.join();

    std::exception_ptr ep = storage->getException();
    if (ep) {
        try {
            std::rethrow_exception(ep);
        } catch (const std::exception& e) {
            logError("Caught exception: " + std::string(e.what()));
        }
    }

    return 0;
}

说明

  1. 日志记录:添加了一个 logError 辅助函数,用于记录错误信息。
  2. 文件处理逻辑分离:将文件处理逻辑封装在 convertFileToUppercase 函数中,使得主函数更加简洁。
  3. 增强错误处理:在尝试打开文件时,增加了对异常的捕捉,并记录详细的错误信息。

通过这样的改进,代码变得更加清晰和易于维护。同时,通过日志记录,我们可以更容易地诊断问题所在。

😍😍 海量H5小游戏、微信小游戏、Web casualgame源码😍😍
😍😍试玩地址: https://www.bojiogame.sg😍😍
😍看上哪一款,需要源码的csdn私信我😍

————————————————

​最后我们放松一下眼睛
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

极致人生-010

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值