
C++构造函数、析构函数可以抛出异常吗?
我们先回忆一下:
-
构造函数:负责“出生”,创建一个对象。
-
析构函数:负责“离开”,销毁一个对象,清理资源。
比如:
class Student
{
public:
Student()
{
cout << "学生出生了" << endl;
}
~Student()
{
cout << "学生毕业了" << endl;
}
};
程序:
Student s;
执行:
学生出生了
…
学生毕业了
一、什么是异常?
生活中:
小明去学校:
出门
↓
坐公交
↓
突然下大雨
↓
无法到学校
“大雨”就是一种特殊情况。
程序中:
int a = 10;
int b = 0;
cout << a / b;
除数不能为 0。
程序遇到了无法正常处理的问题。
这种情况叫:
异常(Exception)
C++中使用:
throw
抛出异常:
throw 错误信息;
使用:
try
{
}
catch()
{
}
接住异常。
二、构造函数可以抛异常吗?
答案:
✅ 可以!
而且非常常见。
1. 为什么构造函数需要抛异常?
因为构造函数负责创建对象。
例如:
我们创建一个文件对象:
class File
{
public:
File(string name)
{
if(name=="")
{
throw "文件名为空";
}
}
};
使用:
try
{
File f("");
}
catch(const char* e)
{
cout << e;
}
输出:
文件名为空
为什么?
因为:
创建对象
|
|
↓
发现无法创建
|
|
↓
抛异常
这个对象根本没有成功出生。
三、构造函数抛异常后,对象存在吗?
这是重点。
答案:
❌ 不存在!
例如:
class Student
{
public:
Student()
{
cout<<"开始创建"<<endl;
throw "创建失败";
}
};
int main()
{
try
{
Student s;
}
catch(…)
{
}
}
执行:
开始创建
然后异常。
对象没有创建成功。
所以:
Student s;
不存在。
四、构造函数抛异常,会调用析构函数吗?
答案:
❌ 不会调用这个对象的析构函数
为什么?
因为:
构造失败
↓
对象没有出生
↓
没有死亡
↓
不用析构
比如:
class A
{
public:
A()
{
cout<<"构造"<<endl;
throw 1;
}
~A()
{
cout<<"析构"<<endl;
}
};
int main()
{
try
{
A a;
}
catch(…)
{
}
}
输出:
构造
没有:
析构
五、但是已经创建成功的成员,会析构吗?
这里是一个重要知识。
看:
class B
{
public:
B()
{
cout<<"B构造"<<endl;
}
~B()
{
cout<<"B析构"<<endl;
}
};
class A
{
B b;
public:
A()
{
cout<<"A构造"<<endl;
throw 1;
}
~A()
{
cout<<"A析构"<<endl;
}
};
执行:
try
{
A a;
}
catch(…)
{
}
过程:
创建A
|
创建成员b
|
B成功出生
|
A构造函数失败
|
A没有创建成功
|
销毁已经创建的b
输出:
B构造
A构造
B析构
注意:
没有:
A析构
但是:
B析构
原因:
对象A没有完整出生。
但是它里面已经出生的成员,需要清理。
六、析构函数可以抛异常吗?
答案:
技术上:
✅ 可以
但是:
实际开发:
❌ 强烈不推荐!
为什么?
因为析构函数什么时候运行?
通常:
程序正常
|
对象离开作用域
|
析构
例如:
{
Student s;
}
离开大括号:
调用析构
但是如果:
析构函数里面抛异常:
~Student()
{
throw "错误";
}
会发生什么?
七、最危险情况:异常里面又遇到析构异常
看:
try
{
Student s;
throw "外部错误";
}
catch(…)
{
}
过程:
发生异常
|
开始寻找catch
|
需要销毁对象s
|
调用s析构函数
|
析构函数又throw
|
两个异常同时存在
C++不知道怎么办。
于是:
程序直接结束:
std::terminate()
简单理解:
两个小朋友同时喊:
小明:
老师,我有问题!
小红:
老师,我也有问题!
老师:
???
程序只能停止。
八、所以C++规定:
析构函数最好:
不要抛异常
原因:
析构函数的任务:
清理东西,不应该制造新的麻烦。
例如:
正确:
~File()
{
close();
}
错误:
~File()
{
throw "关闭失败";
}
九、现代C++推荐写法
C++11以后:
析构函数默认:
noexcept
意思:
这个函数保证不会抛异常。
例如:
class Student
{
public:
~Student() noexcept
{
}
};
如果析构函数真的可能失败怎么办?
不要:
~Student()
{
throw;
}
而应该:
提前处理:
student.close();
例如:
文件:
file.close();
网络:
socket.shutdown();
资源:
提前释放。
十、总结表
| 构造函数 | ✅ 可以 | 常用 |
| 析构函数 | ⚠️ 可以 | 不要 |
| 构造失败 | 对象不存在 | 不会调用自己的析构 |
| 成员已经创建 | 会调用成员析构 | 保证资源释放 |
| 析构抛异常 | 可能导致程序终止 | 避免 |
给小学生的一句话总结:
构造函数像“出生检查”,发现不能出生,可以报告错误(抛异常)。
析构函数像“打扫卫生”,最好安安静静完成任务,不要制造新的问题。
在C++工程中:
-
构造函数抛异常:✅ 很正常
-
析构函数抛异常:❌ 大忌
这也是为什么很多大型C++项目要求:
构造函数可以失败,析构函数必须成功。
这和前面讲的RAII资源管理思想(对象负责管理资源)是紧密相关的,也是C++面向对象非常核心的设计思想。
网硕互联帮助中心





评论前必须登录!
注册