目录
[1. 抛出异常](#1. 抛出异常)
[2. 栈展开](#2. 栈展开)
[3. 查找匹配代码](#3. 查找匹配代码)
[4. 安全问题](#4. 安全问题)
[5. 异常规范](#5. 异常规范)
1. 抛出异常
-
异常抛出后,沿着调用链,在里层或外层都能被处理。
-
捕获 (
catch) 规则:由作用链上类型匹配最近的捕获。
cpp
int func() {
int a; int b;
cin >> a >> b;
try {
if (b == 0) {
string s("divide by 0");
throw s;
} else {
return a / b;
}
}
catch (int s) {
cout << s << endl;
}
}
int main() {
try {
cout << func();
}
catch (const string& s) {
cout << s << endl;
}
catch (...) {
cout << "unknown" << endl;
}
return 0;
}
-
在函数中,若输入 1, 0,由于函数内最近的
catch要求int类型,不匹配,因此继续向下抛出,到main函数中匹配string类型的捕获。 -
catch (...)可以捕获任何类型的异常,作为程序的兜底,防止因为异常找不到匹配的catch而崩溃。
2. 栈展开
-
抛出异常后,由于后面的程序不执行,函数栈帧会不断销毁,直到找到最近的可接受该异常的处理代码。
-
若直到
main函数都没找到可以catch的,程序就会直接报错。
3. 查找匹配代码
-
规则 :多个
catch匹配就选更近的;允许常量转为非常量;允许数组转为指针;允许子类转为父类。 -
在大型项目中,一般会选用子类转为父类的规则。
应用示例
cpp
class basemod {
public:
basemod(const string& errmsg, int id)
:_errmsg(errmsg)
, _id(id) {
}
virtual string what() const {
return _errmsg;
}
int getid() const {
return _id;
}
protected:
string _errmsg;
int _id;
};
class Amod :public basemod {
public:
Amod(const string& errmsg, int id, const string& data)
:basemod(errmsg, id)
, _data(data) { }
virtual string what() const {
string str = "Amod";
str += _errmsg;
str += "->";
str += _data;
return str;
}
protected:
string _data;
};
- 假设在这个项目中有 A 模块(发送模块)继承了基类。
what函数用于生成报错信息,id用于存储报错值。
cpp
catch (const basemod& b) {
cout << b.what() << endl;
}
- 由于
what是虚函数,因此不同的子类在通过基类引用捕获时,可以调用子类重写的what函数,生成不同的报错信息。
模拟发送函数
cpp
void send() {
string mes;
cin >> mes;
for (int i = 0; i < 2; i++) {
try {
if (rand() % 3 == 0) {
throw Amod("网络不稳定", 102, mes);
}
if (rand() % 3 == 1) {
throw Amod("不是对方好友", 103, mes);
}
cout << "成功" << endl;
break;
}
catch (const basemod& b) {
if (b.getid() == 102) {
if (i == 1) throw;
cout << "第" << i + 1 << "次尝试" << endl;
} else {
throw;
}
}
}
}
-
假如我们发送可能遇到两种报错:网络不好以及不是对方好友。
-
网络不好就尝试重新发送,再不行才抛出;不是对方好友则直接抛异常。
-
因此执行循环,当没有报错直接
break,有报错则进入catch (const basemod& b)处理,并根据情况继续抛出。 -
同时,其它模块也可以继承基类,抛出对应的异常。
4. 安全问题
-
在抛出异常后,后面的代码不再执行,可能导致内存释放不会进行。
-
解决方式:使用智能指针。
5. 异常规范
-
C++98:如果一个函数不会抛出异常,就在声明后加
throw()(跟空括号)。 -
C++11:加
noexcept。 -
但由于
noexcept可能与实际异常捕获冲突,且编译器不会严格检查,因此要避免写这种有冲突的代码。 -
同样,
noexcept可以检查这个函数是否会抛异常。

cpp
cout << noexcept(func()) << endl;
int t = 0;
cout << noexcept(t++) << endl;
- 可能会抛异常的函数返回 0,否则返回 1。