继承 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() 是安全的,但不要把它存成裸指针长期持有std::exception 及其派生类可能因 ABI 不兼容而崩溃;此时应改用错误码或跨 ABI 的结构体传信息标准库异常体系不鼓励深度继承,但只要守住 what() 的生命周期、noexcept 约束和虚析构这三点,自定义异常就能稳住。最常翻车的地方,其实是把 what() 写成返回临时字符串的指针——这个坑,几乎每个初学者都踩过一次。