继承 std::exception 是最稳妥的起点,需声明虚析构函数、重写 what() 并确保返回值生命周期可控,避免返回局部变量或临时对象的 c_str()。

继承 std::exception 是最稳妥的起点
直接从 std::exception 派生,能保证所有标准异常处理逻辑(比如 catch (const std::exception&))都兜得住。别图省事用 std::runtime_error 之类二级异常当基类——它本身不带虚析构函数(C++11 前),且语义上已预设了“运行时错误”含义,和你自定义的业务异常(比如 ConfigParseError)不匹配。
实操建议:
- 必须声明虚析构函数:
virtual ~MyException() noexcept override = default; - 重写
what(),返回const char*;别返回局部字符串字面量或临时std::string的c_str() - 构造函数推荐接受
std::string或const char*,内部转存为std::string成员,避免裸指针管理
what() 返回值生命周期必须可控
这是最常踩的坑:在 what() 里返回局部变量的 c_str(),或者返回临时 std::string("xxx").c_str(),结果 catch 侧拿到的是野指针,std::cout 输出乱码甚至崩溃。
正确做法是把错误信息存在类成员里,what() 只返回它的 c_str():
立即学习“C++免费学习笔记(深入)”;
class ValidationError : public std::exception {
private:
std::string msg_;
public:
explicit ValidationError(const std::string& msg) : msg_(msg) {}
const char* what() const noexcept override { return msg_.c_str(); }
};要不要加额外字段?看捕获方是否需要结构化信息
如果只靠错误消息文本就能诊断问题(比如日志记录),那纯 what() 就够了。但若上层要根据错误类型做分支处理(比如重试、降级、告警级别切换),光靠字符串匹配太脆弱。
这时可以加字段,但注意兼容性:
- 加
int code()成员函数比硬编码字符串解析更可靠 - 避免加非 trivial 成员(如含虚函数的子对象),否则可能影响异常对象在栈展开时的构造安全性
- 别为了“看起来专业”塞一堆字段——90% 的自定义异常只需要一个
code和一个what()
抛出时用 throw MyException(...),别传引用或指针
异常对象由 throw 表达式复制构造,所以必须确保可拷贝。传引用(throw &e)或指针(throw new MyException)会导致悬垂、内存泄漏或无法被标准 catch 子句捕获。
常见错误现象:
-
throw std::move(e)—— 移动后原对象状态未定义,且异常对象仍需拷贝,move 无意义 - 在
what()中动态分配内存并返回其指针 —— 异常栈展开期间内存可能已被释放 - 派生类没实现
what(),靠基类默认实现返回空字符串
复杂点在于:异常类型一旦抛出,就脱离定义它的作用域,所有成员必须自持生命周期。最容易被忽略的是字符串内容的存储方式——不是“能不能拼”,而是“拼完之后谁负责活到 catch 完”。











