17370845950

c++怎么实现自定义异常类_c++ 继承std::exception与what方法重写【实战】
继承 std::exception 时必须用类内 std::string 成员保存错误消息并重写 const noexcept 的 what() 方法,否则 what() 返回悬垂指针导致未定义行为;还需显式声明 virtual 析构函数,并以 const& 方式捕获异常。

为什么继承 std::exception 不能只写空构造函数

直接继承 std::exception 并只定义一个空构造函数,会导致 what() 返回的字符串不可控甚至崩溃。因为 std::exception 的默认实现不保存任何消息,其 what() 返回的是未定义行为(常见为指向已销毁栈内存的指针),尤其在抛出后被多次调用时极易出错。

  • 必须确保 what() 返回的 C 字符串生命周期长于异常对象本身存活期
  • 不能返回局部 std::string::c_str() 或临时 std::string 的内部指针
  • 推荐在类内持有一个 std::string 成员,用它来提供稳定地址

如何安全重写 what() 方法(含 const 和 noexcept)

what() 必须声明为 const noexcept,否则无法通过 std::exception 接口被正确调用;同时返回类型必须是 const char*,且指向的内容不能随对象析构而失效。

class FileOpenError : public std::exception {
private:
    std::string message_;
public:
    explicit FileOpenError(const std::string& file)
        : message_("Failed to open file: " + file) {}
const char* what() const noexcept override {
    return message_.c_str();
}

};

  • message_ 是类内 std::string 成员,保证字符串存储在堆上、生命周期与异常对象一致
  • noexcept 是强制要求:C++ 标准规定 std::exception::what()noexcept,子类重写也必须保持相同异常规范
  • 不要在 what() 里拼接新字符串或调用可能抛异常的函数

要不要加 virtual 析构函数?

要。虽然 std::exception 的析构函数已是 virtual,但显式写出能避免误删派生类资源(比如你后续在自定义异常中添加了动态分配的缓冲区或文件句柄)。

  • 即使当前没资源要清理,也建议统一加上 virtual ~FileOpenError() = default;
  • 不加不会立即报错,但若未来扩展异常类携带资源,就容易发生析构不完整、内存泄漏等问题
  • 所有多态基类都应该有 virtual 析构函数——这是 C++ 基本守则,不是可选项

实际使用时容易忽略的细节

捕获时别用值传递,也别漏掉 const&;日志打印前先确认 what() 是否真的可用;跨 DLL 边界抛异常需格外小心。

  • 错误写法:catch (FileOpenError e) —— 触发不必要的拷贝,还可能切片(如果从更深层派生)
  • 正确写法:catch (const FileOpenError& e)catch (const std::exception& e)
  • catch 块里直接用 e.what() 是安全的,但不要把它存成裸指针长期持有
  • Windows 下若异常跨越 DLL 边界(如 DLL 抛出、EXE 捕获),std::exception 及其派生类可能因 ABI 不兼容而崩溃;此时应改用错误码或跨 ABI 的结构体传信息

标准库异常体系不鼓励深度继承,但只要守住 what() 的生命周期、noexcept 约束和虚析构这三点,自定义异常就能稳住。最常翻车的地方,其实是把 what() 写成返回临时字符串的指针——这个坑,几乎每个初学者都踩过一次。