继承std::exception是最稳妥的起点,能确保异常被标准catch块正确捕获;what()返回指针须指向生命周期安全内存;应公开上下文字段而非拼入what()字符串;注意拷贝/移动语义及跨dll限制。

继承 std::exception 是最稳妥的起点
直接从 std::exception 派生,能保证你的异常在所有标准兼容的 catch 块中被正确捕获(比如 catch (const std::exception& e))。别用空基类或自定义接口——C++ 异常处理机制只认类型继承链,不查虚函数表或接口实现。
常见错误是继承 std::runtime_error 或 std::logic_error 后重写 what() 却忘了调用父类构造:结果 e.what() 返回空指针或乱码。正确做法是把描述信息传给父类构造器,让它管理内存和字符串生命周期:
class FileOpenError : public std::runtime_error {
public:
explicit FileOpenError(const std::string& path)
: std::runtime_error("Failed to open file: " + path) {}
};
what() 返回的指针必须指向生命周期安全的内存
这是最容易崩溃的地方。如果你在 what() 里返回局部变量的 c_str(),或者返回临时 std::string 的 c_str(),程序大概率在 catch 处访问野指针。
安全做法只有两种:
- 像上面例子那样,让父类(如
std::runtime_error)持有字符串对象,它内部已确保what()返回的 C 字符串在整个异常对象生命周期内有效 - 如果必须自己管理,就用
mutable std::string缓存格式化结果,并在what()中返回其c_str()
别用 static 缓冲区——多线程下会互相覆盖;也别 new 内存然后不 delete——异常栈展开时不会自动析构你 new 出来的字符数组。
带额外上下文字段时,别把它们塞进 what() 字符串
比如你想记录错误码、文件路径、行号,直接拼进 what() 输出看似省事,但后续难以结构化提取。用户想做日志分级、错误分类或自动重试,就得用字符串解析——脆弱又低效。
更好的方式是公开成员变量或访问器:
class NetworkTimeoutError : public std::runtime_error {
int m_timeout_ms;
std::string m_host;
public:
NetworkTimeoutError(int ms, const std::string& host)
: std::runtime_error("Network timeout"),
m_timeout_ms(ms), m_host(host) {}
int timeout() const { return m_timeout_ms; }
const std::string& host() const { return m_host; }
};
这样调用方可以:catch (const NetworkTimeoutError& e) { if (e.timeout() > 5000) log_fatal(...); } ——逻辑清晰,类型安全,无解析开销。
注意异常对象的拷贝与移动语义
抛出异常时,对象可能被复制(C++11 前)或移动(C++11 起)。如果你的异常类包含非 trivial 成员(比如含 mutex、FILE*、unique_ptr),要显式定义拷贝/移动构造函数和赋值运算符,否则编译可能失败,或运行时行为未定义。
更简单的方案是:只用 std::string、int、enum 这类 trivial 类型作成员;若必须持资源,优先用 std::unique_ptr(它有默认移动语义)而非裸指针。
另外,别在异常类里放虚函数以外的虚成员——异常对象大小和布局敏感,虚函数表指针会增加体积,且多数异常根本不需多态行为。
真正麻烦的是跨 DLL 边界抛异常,这时连 std::exception 继承都可能失效——不同模块的 type_info 不一致,catch 会失败。这种场景下,老老实实用错误码 + 日志,别抛。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











