1. 概念
C语言中通过错误码的形式分类各种错误,C++11引入了在异常时抛出对象的机制,从而获取更加详细的错误信息。
2. 抛出 / 捕获异常
throw抛出异常,catch捕获异常- 抛出对象的类型和调用链决定了进入哪个
catch处理 throw执行后直接跳到与之匹配的catch,后面的语句不执行- 抛出对象后会生成
拷贝,在catch后销毁(类似传值返回)
使用示例:
double Divide(int a, int b)
{
try
{
if (b == 0)
{
string s = "Divided by zero condition!" ;
throw s;
}
else
{
return (double)a / (double)b;
}
}
catch (const string& errmsg)
{
cout << "Divide" << ":" << errmsg << endl;
}
return 0;
}
int main()
{
int x, y; cin >> x >> y;
Divide(x, y);
return 0;
}
发生异常,抛出了string对象s:

3. 查找匹配
3.1 捕获规则
catch捕获异常的规则:
- 在
调用链中 - 与该对象
类型匹配 - 距离最近
- 如果到
main仍未找到,则调用std::terminate函数终止程序 - 找到匹配的
catch语句->执行catch代码块->执行catch后代码
使用示例:
double Divide(int a, int b)
{
try
{
if (b == 0)
{
string s = "Divided by zero condition!" ;
throw s;
}
else
{
return (double)a / (double)b;
}
}
catch (const char& errmsg) //不同类型
{
cout << "Divide" << ":" << errmsg << endl;
}
return 0;
}
void Func(int a, int b)
{
try
{
cout << Divide(a, b) << endl;
}
catch (const string& str) //同类型最近
{
cout << "Func" << ":" << str << endl;
}
}
int main()
{
int x, y; cin >> x >> y;
Func(x, y);
return 0;
}
输出:

这里可以看到跳过了Divide的catch,匹配到了调用链上层Func函数的catch。
3.2 栈展开
在查找匹配的catch语句过程中会发生层层展开,步骤如下:
- 检查
throw是否在try划定的范围内 - 在则查找匹配的
catch - 当前函数未找到,跳出到外层调用链函数查找
- 直到
main函数未找到,调用std::terminate结束
调用链的展开:

3.3 查找匹配处理代码
catch参数为基类指针时,可以同时捕捉基类对象和派生类对象- 实际应用中,
main函数内部会另设catch(...)语句,用于捕获其他异常,类型未知
另外,catch应按照引用捕获,避免拷贝开销和派生类切片问题。
下面是一个“服务器系统”的异常处理场景模拟:
class Exception
{
public:
Exception(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 SqlException : public Exception
{
public:
SqlException(const string& errmsg, int id, const string& sql)
: Exception(errmsg, id)
, _sql(sql)
{}
virtual string what() const
{
string str = "SqlException:";
str += _errmsg;
str += "->";
str += _sql;
return str;
}
private:
const string _sql;
};
class CacheException : public Exception
{
public:
CacheException(const string& errmsg, int id)
: Exception(errmsg, id)
{}
virtual string what() const
{
string str = "CacheException:";
str += _errmsg;
return str;
}
};
class HttpException : public Exception
{
public:
HttpException(const string& errmsg, int id, const string& type)
: Exception(errmsg, id)
, _type(type)
{}
virtual string what() const
{
string str = "HttpException:";
str += _type;
str += ":";
str += _errmsg;
return str;
}
private:
const string _type;
};
void SQLMgr()
{
if (rand() % 7 == 0)
{
throw SqlException("权限不⾜", 100, "select * from name = '张三'");
}
else
{
cout << "SQLMgr 调⽤成功" << endl;
}
}
void CacheMgr()
{
if (rand() % 5 == 0)
{
throw CacheException("权限不⾜", 100);
}
else if (rand() % 6 == 0)
{
throw CacheException("数据不存在", 101);
}
else
{
cout << "CacheMgr 调⽤成功" << endl;
}
SQLMgr();
}
void HttpServer()
{
if (rand() % 3 == 0)
{
throw HttpException("请求资源不存在", 100, "get");
}
else if (rand() % 4 == 0)
{
throw HttpException("权限不⾜", 101, "post");
}
else
{
cout << "HttpServer调⽤成功" << endl;
}
CacheMgr();
}
int main()
{
srand(time(0));
while (1)
{
this_thread::sleep_for(chrono::seconds(1)); //每隔1秒执行一次循环体
try
{
HttpServer(); //网页端
}
catch (const Exception& e) // 参数接收基类,基类对象和派生类对象都可以被捕获
{
cout << e.what() << endl;
}
catch (...)
{
cout << "Unkown Exception" << endl;
}
}
return 0;
}
//异常重新抛出
//因为网络异常发不出去则就需要捕获异常再重新抛出;错误并非源自网路差,也要重新抛出。
void _SeedMsg(const string& s)
{
if (rand() % 2 == 0)
{
throw HttpException("⽹络不稳定,发送失败", 102, "put");
}
else if (rand() % 7 == 0)
{
throw HttpException("你已经不是对象的好友,发送失败", 103, "put");
}
else
{
cout << "发送成功" << endl;
}
}
void SendMsg(const string& s)
{
// 发送消息失败,则再重试3次
for (size_t i = 0; i < 4; i++)
{
try
{
_SeedMsg(s);
break;
}
catch (const Exception& e)
{
if (e.getid() == 102)
{
// 重试三次以后否失败了,则说明网络太差了,重新抛出异常
if (i == 3)
throw;
cout << "开始第" << i + 1 << "重试" << endl;
}
else //并非网络异常引起的错误,交由外层函数处理(main)
{
throw;
}
}
}
}
int main()
{
srand(time(0));
string str;
while (cin >> str)
{
try
{
SendMsg(str);
}
catch (const Exception& e)
{
cout << e.what() << endl << endl;
}
catch (...)
{
cout << "Unkown Exception" << endl;
}
}
return 0;
}
部分输出如下:

4. 安全问题
《c++effective》条款:别让异常逃离析构函数。
若开辟了资源,在资源释放前抛出异常,则会发生内存泄漏的安全问题。解决方法是使用智能指针,这点后续文章会详细讲解。
5. 规范
- C++98:
throw()表示不抛异常,throw(参数...)表示抛出指定类型的异常 - C++11:
noexcept作为关键字放在函数后表示不抛异常;作为运算符使用,判断函数是否可能会抛异常(true不抛,反之false
使用示例:
//作为关键字
double Divide(int a, int b)
{
try
{
if (b == 0)
{
string s = "Divided by zero condition!" ;
throw s;
}
else
{
return (double)a / (double)b;
}
}
catch (const string& errmsg)
{
cout << "Divide" << ":" << errmsg << endl;
}
return 0;
}
int Add(int a, int b) noexcept
{
return a + b;
}
int main()
{
//作为运算符
int i;
cout << noexcept(Add(1, 2)) << endl;
cout << noexcept(Divide(1, 0)) << endl;
cout << noexcept(++i) << endl;
return 0;
}
输出:

感谢阅读,我们下篇见。

1592

被折叠的 条评论
为什么被折叠?



