必须为自定义异常显式声明虚析构函数并用成员std::string存储消息,否则用基类指针捕获时会资源泄漏或what()返回悬空指针;优先使用std::runtime_error等标准异常类。

为什么继承 std::exception 不能只重写 what()?
因为 std::exception 的 what() 是 virtual const char*,但它的析构函数不是 virtual —— 这意味着如果你用基类指针捕获派生异常对象,而派生类有自定义成员(比如 std::string),析构时会不调用派生类析构函数,导致资源泄漏或未定义行为。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 必须显式声明
virtual ~MyException() noexcept = default;(C++11 起推荐加noexcept) - 不要在
what()返回局部变量地址(比如临时std::string::c_str()),否则返回的指针可能悬空 - 典型错误现象:
std::terminate被调用、程序崩溃、what()返回乱码或空字符串
std::runtime_error 和手写继承 std::exception 哪个更稳妥?
优先用 std::runtime_error 或其兄弟类(如 std::logic_error),它们已正确实现虚析构、持有 std::string 并安全返回 c_str()。自己从 std::exception 派生仅在需要独特类型层级(比如想被 catch (const MyDomainError&) 精确捕获)时才必要。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 如果只是带消息的异常,直接用
throw std::runtime_error("bad file format"); - 如果需要自定义类型 + 消息 + 额外字段(如错误码
int code_),再继承std::exception - 注意:所有标准异常类(包括
std::runtime_error)都继承自std::exception,所以catch (const std::exception&)仍能兜底
如何让自定义异常支持 std::string 消息且避免内存问题?
关键不是“存不存 std::string”,而是“怎么返回 const char*”。不能返回局部 std::string 的 c_str(),也不能返回堆分配后忘了释放的指针。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 在类内持有一个
std::string成员(如std::string msg_;),在构造时初始化它 -
what()直接返回msg_.c_str()—— 因为msg_是对象生命周期内的成员,指针有效 - 不要手动
new char[]拼接字符串;也不要返回std::to_string(x).c_str()这种临时对象的指针 - 示例片段:
class FileOpenError : public std::exception { std::string msg_; public: explicit FileOpenError(const std::string& path) : msg_("Failed to open: " + path) {} const char* what() const noexcept override { return msg_.c_str(); } virtual ~FileOpenError() noexcept = default; };
抛出和捕获时的类型切片风险
如果抛出的是临时对象(throw MyException("x");),而你用非引用方式捕获(catch (MyException e)),会触发拷贝构造 —— 如果没定义或禁用了拷贝,编译失败;如果拷贝了但基类析构不虚,就切片掉派生部分。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 始终用
const引用捕获:catch (const MyException& e) - 确保自定义异常类可拷贝(默认生成即可),除非你明确禁用并改用智能指针包装(极少需要)
- 检查编译器警告:Clang/GCC 加
-Wcatch-value会提醒你捕获方式可能导致切片
实际写的时候,最常被忽略的是虚析构和 what() 返回值的生命周期绑定。这两点一错,异常看起来“抛出了”,但捕获后 e.what() 不是空就是乱码,调试时容易绕远路。










