继承 std::exception 或其派生类(如 std::runtime_error、std::logic_error)最稳妥,需重写 noexcept 的 what() 并避免析构抛异常。

继承 std::exception 是最稳妥的起点
直接从 std::exception 派生,能保证所有标准异常处理机制(比如 catch(const std::exception&))都接得住你的异常。别图省事用 class MyError {} 空基类——它和标准异常体系完全脱节,catch(...) 虽然能兜底,但会丢失类型信息和语义。
- 必须重写
what()成员函数,返回const char*;返回值建议指向内部std::string的c_str(),避免悬垂指针 - 不要在
what()里抛异常、分配内存或调用虚函数——它可能在栈展开中被反复调用,环境不可靠 - 构造函数推荐用
std::string初始化内部消息,而不是const char*,避免编码/生命周期问题
std::runtime_error 和 std::logic_error 更适合快速落地
如果你的异常属于“运行时才暴露的问题”(比如文件打不开、网络超时),直接继承 std::runtime_error;如果是“逻辑错误”(比如传了负数给只接受正数的函数),用 std::logic_error。它们已经实现了 what(),且语义清晰,调用方更容易判断错误性质。
- 二者都接受
std::string或const char*构造,内部自动管理字符串存储 - 别为了“看起来更定制”而绕开它们去继承
std::exception——除非你需要额外字段(如错误码、上下文 ID) - 注意:
std::logic_error子类通常暗示是编程错误,应尽量在开发/测试阶段暴露,而非生产环境静默吞掉
带错误码的异常类要小心 errno 和平台差异
如果想让异常携带系统级错误码(比如 open() 失败后的 errno),别直接存 errno 全局变量——它可能被后续系统调用覆盖。必须在检测到错误后立刻保存。
- Linux/macOS 下可用
strerror_r()把错误码转成可读字符串;Windows 用FormatMessage(),二者行为不兼容,别写跨平台假设 - 错误码本身建议用
int存,别用errno宏——它不是常量,不同平台定义不同 - 如果异常需要序列化或跨线程传递,避免依赖
errno;改用明确传入的错误码参数构造
析构函数里千万别抛异常
自定义异常类的析构函数必须是 noexcept(C++11 起默认如此),否则一旦在栈展开过程中析构抛出新异常,程序直接调用 std::terminate() 终止——连 catch(...) 都没机会执行。
立即学习“C++免费学习笔记(深入)”;
- 所有资源清理(如释放堆内存、关闭句柄)必须确保不会失败;若可能失败,改用日志记录+忽略,而非抛异常
- 如果异常类内部用了
std::string,它的析构是安全的(标准库保证noexcept);但自己加的std::vector或智能指针一般也没问题 - 检查编译器警告:Clang/GCC 在析构函数里检测到 throw 表达式会报
throw in destructor类似提示
最常被忽略的是异常对象自身的生命周期管理——它可能被复制多次(尤其在跨 catch 块传播时),所以成员变量尽量用值语义,避免裸指针或外部引用。









