文章目录
前言
许多的学员对异常处理视而不见,程序里很少考虑异常情况。一部分人甚至根本就不考虑,以为程序总是能以正确的途径运行。例如我们有的学员调用fopen打开一个文件后,立马就开始进行读写操作,根本就不考虑文件是否正常打开了。这种习惯一定要改掉,纵使你再不愿意!这是软件健壮性的需要!异常处理不是浪费时间!
一、异常是什么?
异常是面向对象语法处理错误一种方式
二、C语言传统的处理错误的方式有哪些呢?
1.返回错误码
有些API接口都是把错误码放到errno中
2.终止程序
比如发生越界等严重问题时,我们也可以主动调用exit(xx) assert;
三、传统的处理错误的缺陷?
1.拿到错误码,需要查找错误码表,才知道是什么错误。
2.如果一个函数是通过返回值拿数据,发生错误时很难处理。
T& operator[](int index)
{
// 如果index超出的容器范围,如何返回?
}
3.如果调用的函数栈很深,一层层返回错误码,整个处理很难受
四、异常的使用?
1.异常的抛出和捕获
原则:
1.异常是根据抛出对象而引发的,该对象的类型决定了应该激活哪个catch的处理代码
2.被选中的处理代码是与该对象类型匹配且抛出异常位置最近的那一个.
3.抛出异常对象后,会生成一个异常对象的拷贝,这个临时对象会在被catch以后被销毁
4.catch(…)可以捕获任意类型的异常
5.实际中,我们可以抛出的派生类对象,使用基类捕获.
在函数调用链中异常栈展开匹配原则
- 首先检查throw本身是否在try块内部,如果是再查找匹配的catch语句。
- 如果到达main函数的栈,依旧没有匹配的,则终止程序
namespace bit
{
int div(int n, int m)
{
if (m == 0)
{
//throw "发生除0错误";
throw string("发生除0错误"); // throw 可以抛出任意类型的对象
//throw - 1;
// 直接跳转到catch匹配的地方
}
return n / m;
}
}
void f1()
{
try{
int n, m;
cin >> n >> m;
cout << bit::div(n, m) << endl;
}
catch (const string& err)
{
cout << __LINE__ << err << endl;
}
}
int main()
{
try
{
f1();
}
catch (int err)
{
cout <<err << endl;
}
catch (const string& err)
{
cout << __LINE__ << err << endl;
}
catch (...) // 捕获没有匹配的任意类型的异常,避免异常没捕获时程序直接终止了
{
cout << "未知异常" << endl;
}
}
2.异常重新抛出
异常可能会导致异常安全问题
double Division(int a, int b) {
// 当b == 0时抛出异常
if (b == 0)
{
throw "Division by zero condition!";
}
return (double)a / (double)b;
}
void Func()
{
// 这里可以看到如果发生除0错误抛出异常,另外下面的array没有得到释放。
// 所以这里捕获异常后并不处理异常,异常还是交给外面处理,这里捕获了再
// 重新抛出去。
int* array = new int[10];
try {
int len, time;
cin >> len >> time;
cout << Division(len, time) << endl;
}
catch (...)
{
cout << __LINE__<<"delete []" << array << endl;
delete[] array;
throw;
}
// ...
cout <<__LINE__<< "delete []" << array << endl;
delete[] array;
}
int main()
{
try
{
Func();
}
catch (const char* errmsg)
{
cout <<__LINE__<< errmsg << endl;
}
return 0;
}
五、自定义异常体系
以实际中都会定义一套继承的规范体系。这样大家抛出的都是继承的派生类对象,捕获一个基类就可以了
class Exception
{
public:
Exception(const char* errmsg, int errid)
: _errmsg(errmsg)
,_errid(errid)
{}
virtual string what() = 0;
protected:
int _errid; // 错误码
string _errmsg; // 错误描述
//stack<string> _st; // 调用栈帧
};
class SqlException : public Exception
{
public:
SqlException(const char* errmsg, int errid)
:Exception(errmsg, errid)
{}
virtual string what()
{
return "数据库错误:" + _errmsg;
}
};
class NetworkException : public Exception
{
public:
NetworkException(const char* errmsg, int errid)
:Exception(errmsg, errid)
{}
virtual string what()
{
return "网络错误:" + _errmsg;
}
};
void ServerStart()
{
// 模拟一下出现问题抛异常报错
if (rand() % 11 == 0)
throw SqlException("数据库启动失败", 1);
if (rand() % 7 == 0)
throw NetworkException("网络连接失败", 3);
cout << "正常运行" << endl;
}
int main()
{
for (size_t i = 0; i < 100; i++)
{
try
{
ServerStart();
}
catch (Exception& e)
{
cout << e.what() << endl;
}
catch (...)
{
cout << "未知异常" << endl;
}
}
return 0;
}
总结
/异常可能会导致异常安全问题
//new / fopen / lock
//
//func(); // 如果抛异常就会有异常安全问题 -> 捕获重新抛出 or RAII 解决
//
//delete/fclose/ unlock
函数规范一下,如果要抛异常,你说明清楚,不抛异常也说明一下, 但是现实中,很多人嫌麻烦,不遵守规范
这里表示这个函数会抛出A/B/C/D中的某种类型的异常
// void fun() throw(A, B, C, D);
这里表示这个函数只会抛出bad_alloc的异常
void* operator new (std::size_t size) throw (std::bad_alloc);
// 这里表示这个函数不会抛出异常
void* operator delete (std::size_t size, void* ptr) throw();
void* operator delete (std::size_t size, void* ptr) noexcept;
异常的优缺点总结:
优点:
1、清晰的包含错误信息
2、面对T operator[](int i)这样函数越界错误,异常可以很好的解决
3、多层调用时,里面发生错误,不再需要层层处理,最外层直接捕获即可
4、很多第三方库都是用异常,我们也使用异常可以更好的使用他们。比如:boost、gtest、gmock
缺点:
1、异常会导致执行流乱跳。会给我调试分析程序bug带来一些困难。
2、C++没有GC,异常可能到导致资源泄露等异常安全问题,需要学会使用RAII来解决。
3、C++的库里面的异常体系定义不太好用,很多公司都会选择自己定义。
4、C++的异常语言可以抛任意类型的异常,如果项目中没有做很好规范管理,那么会非常的混乱,所以一般需要定义出继承体系的异常规范。
异常整体而言还是一个利大于弊的东西,所以实际日常练习或者小项目,不太使用,公司一般还是会选择异常来处理错误