不崩溃的系统级C++代码关键在于异常发生时资源不泄漏、对象可析构、状态可回退;必须遵循RAII、析构函数声明noexcept、赋值采用copy-and-swap,且每种资源需专属noexcept管理类。

写出不崩溃的系统级 C++ 代码,关键不是“避免抛异常”,而是让每次异常发生时——资源不泄漏、对象可析构、状态可回退。Core Guidelines 明确要求:所有资源管理必须基于 RAII,所有析构函数必须 noexcept,所有强保证操作优先用 copy-and-swap。
为什么析构函数必须加 noexcept?
栈展开(stack unwinding)期间若析构函数意外抛出异常,C++ 标准强制调用 std::terminate() —— 程序直接终止,毫无回旋余地。这不是理论风险,而是真实 crash 的常见源头。
- 所有自定义析构函数应显式声明为
~MyClass() noexcept,哪怕它只调用fclose()或delete - 切勿在析构函数中调用可能抛异常的函数(如
std::ofstream::close()可能抛std::ios_base::failure;改用std::ofstream::close()+ 检查failbit) -
标准库容器、智能指针、
std::lock_guard的析构函数均已标记noexcept,可放心依赖
operator= 怎么写才真正强异常安全?
裸写赋值运算符极易破坏强保证:比如先 delete[] data 再 new,中间抛异常就导致悬空指针或内存泄漏。Core Guidelines 推荐唯一可靠模式:copy-and-swap。
class Buffer {
std::unique_ptr data_;
size_t size_;
public:
Buffer& operator=(Buffer other) noexcept { // 注意:参数传值,自动拷贝
swap(*this, other); // swap 是 noexcept 的
return *this;
}
friend void swap(Buffer& a, Buffer& b) noexcept {
using std::swap;
swap(a.data_, b.data_);
swap(a.size_, b.size_);
}
};
- 参数用值传递(
Buffer other),触发拷贝构造——失败则原对象完全不受影响 -
swap必须是noexcept,且只交换指针/整数等基础成员,不涉及内存分配 - 无需手动释放旧资源,旧资源随临时对象
other在函数退出时自动析构
RAII 不是“用智能指针”就完事,而是“每种资源都得有专属类”
很多团队误以为用了 std::unique_ptr 就算 RAII 完成,结果遇到文件句柄、POSIX 信号量、GPU buffer、自定义锁等非内存资源时仍泄漏。Core Guidelines 强调:RAII 是接口契约,不是语法糖。
立即学习“C++免费学习笔记(深入)”;
- 对每个低层资源(如
int fd、pthread_mutex_t*),必须封装为独立类,构造获取、析构释放 - 禁用拷贝(
= delete),移动构造/赋值必须noexcept - 不要把多种资源塞进一个类里——例如同时管理文件和网络 socket 的类,违反“单一职责”,增加异常路径复杂度
- 示例:文件句柄类应像
std::fstream那样,在析构时静默关闭,不抛异常
最常被忽略的一点:异常安全不是靠“catch 住所有异常”来实现的,而是靠资源绑定到作用域、状态变更延后到无异常操作、以及严格限制 noexcept 边界。一旦在析构或 swap 中出现未声明的异常,系统级稳定性就已实质崩塌——而这种问题往往在压力测试或日志关闭时才暴露。










