如何在 C++20 中实现一个自定义异常类并在错误处理中使用 `std::exception_ptr`

在 C++ 的错误处理体系中,异常类是核心。标准库提供了基类 std::exception,但在实际项目中往往需要根据业务逻辑定义更细粒度的异常。C++20 通过引入 std::source_location[[nodiscard]] 等特性,为自定义异常提供了更多便利。本文将演示:

  1. 定义一个可携带错误码、错误消息和源代码位置信息的异常类
  2. 使用 std::exception_ptr 捕获并重抛异常
  3. try–catch 块中优雅地打印异常信息

1. 设计异常类

#include <exception>
#include <string>
#include <source_location>
#include <iostream>
#include <format>

class MyException : public std::exception {
public:
    enum class Code {
        INVALID_ARGUMENT,
        OUT_OF_RANGE,
        UNKNOWN
    };

    MyException(Code code,
                std::string message = {},
                std::source_location loc = std::source_location::current())
        : code_(code),
          message_(std::move(message)),
          file_(loc.file_name()),
          line_(loc.line()),
          col_(loc.column()) {}

    // 重写 what(),返回完整错误描述
    [[nodiscard]] const char* what() const noexcept override {
        if (what_msg_.empty()) {
            what_msg_ = std::format(
                "MyException: {} (code: {}), at {}:{}:{}",
                message_,
                static_cast <int>(code_),
                file_, line_, col_
            );
        }
        return what_msg_.c_str();
    }

    Code code() const noexcept { return code_; }

private:
    Code code_;
    std::string message_;
    std::string file_;
    int line_;
    int col_;
    mutable std::string what_msg_;
};

说明

  • std::source_location 自动捕获抛出点的文件名、行号和列号,方便定位。
  • [[nodiscard]] 用于标记 what() 的返回值不可忽略,避免遗漏错误信息。
  • std::format(C++20)用于构造错误字符串,简洁且安全。

2. 抛出异常并使用 std::exception_ptr

void risky_operation(int value) {
    if (value < 0) {
        throw MyException(MyException::Code::INVALID_ARGUMENT,
                          "value must be non‑negative");
    } else if (value > 100) {
        throw MyException(MyException::Code::OUT_OF_RANGE,
                          "value exceeds maximum allowed");
    }
    std::cout << "Operation succeeded with value " << value << '\n';
}

在调用代码中:

#include <exception>

int main() {
    try {
        risky_operation(-5);
    } catch (...) {
        // 捕获所有异常,保留异常指针
        std::exception_ptr eptr = std::current_exception();
        std::cout << "Caught an exception. Re‑throwing to outer handler.\n";

        try {
            if (eptr) std::rethrow_exception(eptr);
        } catch (const MyException& e) {
            std::cerr << "Handled MyException: " << e.what() << '\n';
        } catch (const std::exception& e) {
            std::cerr << "Handled std::exception: " << e.what() << '\n';
        } catch (...) {
            std::cerr << "Unhandled unknown exception.\n";
        }
    }
}

要点

  • std::current_exception() 捕获当前异常并返回 std::exception_ptr
  • std::rethrow_exception() 可在不同作用域重抛,保持异常栈完整。
  • 通过多重 catch 可以分别处理自定义异常和标准异常,确保错误信息完整可读。

3. 运行结果示例

Caught an exception. Re‑throwing to outer handler.
Handled MyException: MyException: value must be non‑negative (code: 0), at /path/to/file.cpp:78:5

4. 小结

  • 自定义异常类:继承 std::exception,添加业务字段、错误码和源代码位置。
  • std::source_location:自动收集抛出点信息,简化错误追踪。
  • std::exception_ptr + rethrow_exception:实现跨作用域异常传递,保持错误栈完整。
  • C++20:利用 std::format 和属性 [[nodiscard]] 提升代码可读性与安全性。

通过上述模式,你可以在 C++20 项目中构建一个健壮、可追踪且易于维护的异常处理体系。

发表评论