构造函数抛异常会终止构造并自动析构已构造的成员和基类;new 表达式失败时内存自动释放;需显式声明 noexcept(false) 允许异常;必须用 raii 避免裸指针泄漏。

构造函数抛异常会导致对象未完全构造
构造函数里抛异常,C++ 会立即终止对象的构造过程,并自动调用已成功构造的成员变量和基类的析构函数(按构造逆序)。这点和普通函数完全不同——new 表达式若中途抛异常,operator new 分配的内存也会被自动释放(前提是没重载或没禁用 nothrow 版本)。
常见错误是以为“对象指针拿到了就能用”,比如:
MyClass* p = new MyClass(); // 若构造中抛异常,p 不会赋值,这行直接跳到 catch
所以别在 catch 块里试图访问 p,它根本没被初始化。
必须显式声明 noexcept(false) 才能抛异常
C++11 起,所有函数默认隐式为 noexcept(true),包括构造函数。如果没写 noexcept 且内部 throw,编译器可能报错(取决于标准库实现和编译器严格程度),更常见的是运行时调用 std::terminate。
正确做法是显式允许异常:
class MyClass {
public:
MyClass(int x) {
if (x
- 不要依赖“没写就是可以抛”——不同标准库对
std::vector::emplace_back等容器操作是否要求构造函数noexcept有差异,不显式标注容易在插入时触发意外终止 - 若类有虚函数、继承自带异常规范的基类,更要检查基类构造是否也允许抛异常
资源泄漏风险比你想象中高
手动 new + 构造函数抛异常一般安全,但一旦混入裸指针管理,就很容易漏掉清理:
class BadExample {
int* data;
public:
BadExample(size_t n) : data(new int[n]) { // ✅ new 成功
if (n > 1000) throw std::runtime_error("too big");
// ❌ 异常抛出前 data 没被 delete,也没 RAII 封装 → 内存泄漏
}
};
解决方法只有两个:
- 用 RAII 容器替代裸指针,如
std::vector<int></int>或std::unique_ptr<int></int> - 或者把资源分配挪到构造函数体外(工厂函数),构造函数只做无异常逻辑
尤其注意:成员初始化列表中调用可能抛异常的函数(比如 std::string 构造、std::ifstream 打开文件),一样触发上述资源清理规则——只要初始化完成,就会被析构;没完成,就不会析构。
std::vector 和 std::make_unique 对构造异常的处理差异
std::vector::emplace_back 在元素构造失败时会回滚:已构造的元素被析构,size 不变,capacity 可能不变或收缩(取决于实现);而 std::make_unique<t>(args...)</t> 若 T 构造抛异常,unique_ptr 根本不会生成,堆内存也被自动释放。
但要注意一个坑:
-
std::vector<:unique_ptr>> v; v.emplace_back(std::make_unique<t>(bad_args));</t></:unique_ptr>—— 这里有两个可能抛异常的点:std::make_unique内部的new,以及T的构造。两者都安全,因为make_unique是原子的 - 但写成
v.emplace_back(new T(bad_args))就危险了:先new T(可能成功),再传给emplace_back(构造unique_ptr时若抛异常,裸指针就泄漏了)
所以永远优先用 std::make_unique / std::make_shared,而不是裸 new。
真正难缠的是跨线程或信号上下文里的构造异常——C++ 标准不保证异常安全传播到信号处理器,这种场景最好避开构造函数抛异常。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











