目录
一,异常的概念
二.异常的抛出与捕获
三,异常安全
四,自定义异常体系
五,异常的优缺点
一,异常的概念
异常是一种处理错误的方式,当函数遇到一个无法解决的问题,就抛出异常,让函数的调用者或者间接调用者来处理这个错误。
1.throw:抛出异常,throw是一个关键字
2.try:try 块中的代码标识将被激活的特定异常,它后面通常跟着一个或多个 catch 块。
3.catch:在您想要处理问题的地方,通过异常处理程序捕获异常.catch 关键字用于捕获异常,可以有多个catch进行捕获。
二.异常的抛出与捕获
1,异常是通过抛出对象而引发的,该对象的类型决定了应该激活哪个catch的处理代码。
2,被选中的处理代码是调用链中与该对象类型匹配且离抛出异常位置最近的那一个。
3,抛出异常对象后,会生成一个异常对象的拷贝,因为抛出的异常对象可能是一个临时对象,所以会生成一个拷贝对象,这个拷贝的临时对象会在被catch以后销毁。(这里的处理类似于函数的传值返回)
4, catch(...)可以捕获任意类型的异常,问题是不知道异常错误是什么
5. 实际中抛出和捕获的匹配原则有个例外,并不都是类型完全匹配,可以抛出的派生类对象,使用基类捕获
a,对象抛出,catch捕获
#include#include using namespace std; void f1() { int i,j; cin>>i>>j; if(j==0) { throw 1;//抛出异常,异常为1 } cout< void f1() { int i,j; cin>>i>>j; if(j==0) { throw string ("除零错误"); } cout<三,异常安全
请看如下代码,//这种捕获异常容易导致的问题在于,如果f3异常,后续代码不执行,
导致p2未释放,p3未正常关闭,产生内存泄露
此时就需要想其他办法,此时我们创建一个类来解决问题,
类出作用域就析构,不再担心内存泄露的问题int *p2=nullptr; FILE *p3=nullptr; void f2() { p2=(int*)malloc(40); if(p2==nullptr) { throw string ("开空间失败"); } } void f3() { FILE*p3=fopen("test.txt","r"); if(p3==nullptr) { throw string ("打开文件失败"); } } int main() { try{ f2(); f3(); //这种捕获异常容易导致的问题在于,如果f2异常,f3和后续代码不执行, //导致p2未释放,p3未正常关闭,产生内存泄露 //此时就需要想其他办法,此时我们创建一个类来解决问题, //类出作用域就析构,不再担心内存泄露的问题 free(p2); fclose(p3); } catch (string str) { cout<如下面代码所示,通过创建自定义类实现对异常抛出和资源的回收处理。
class myexception { public: myexception(int errid,const string& msg) :_errid(errid) ,_str(msg) {} int Geterrid() { return _errid; } const string& what() const { return _str; } private: int _errid; string _str; }; int *p2=nullptr; FILE *p3=nullptr; void f2() { p2=(int*)malloc(40); if(p2==nullptr) { throw myexception (1,"开空间失败"); } } void f3() { FILE*p3=fopen("test.txt","r"); if(p3==nullptr) { throw myexception (2,"打开文件失败"); } } int main() { try { f2(); f3(); } catch(const myexception& e) { cout<四,自定义异常体系
实际使用中很多公司都会自定义自己的异常体系进行规范的异常管理,因为一个项目中如果大家随意抛异常,那么外层的调用者基本就没办法玩了,所以实际中都会定义一套继承的规范体系。这样大家抛出的都是继承的派生类对象,捕获一个基类就可以了。
如下代码所示,子类继承父类,重写虚函数what,返回类型错误的原因,更清晰,方便人们查错。
在公司中我们做项目的时候,可能会有多个人来部署 所以我们需要写一个基类,来让其他的子类继承他, 当捕获的时候,只需要捕获基类即可 class myexception { public: myexception(int errid,const string& msg) :_errid(errid) ,_str(msg) {} int Geterrid() { return _errid; } virtual string what() const { return _str; } protected: int _errid; string _str; }; class Sqlexception:public myexception { public: Sqlexception(const int errid,const char* str1,const char*sql) :myexception(errid,str1) ,_sql(sql) {} virtual string what()const { string str3="Sqlexception"; str3+=_str; str3+=" "; str3+=_sql; return str3; } protected: string _sql; }; class cacheexception:public myexception { public: cacheexception(const int errid,const char *msg) :myexception(errid,msg) {} virtual string what()const { string str1="cacheexception"; str1+=_str; return str1; } protected: }; void f1() { int i; cin>>i; if(i==0) { throw cacheexception(1,"未开放内存"); } } void f2() { int i; cin >> i; if (i == 0) { throw Sqlexception(1, "数据库查询失败", "select * from t_student"); } } int main() { try { f1(); f2(); } catch(myexception&e) { cout<五,异常的优缺点
优点:
1.相比较错误码,能展示出更多信息,方便调用者定位程序的bug。
2.很多第三方库都包含, 那么我们也使用这些第三方库,也得使用异常
3.很多测试框架都使用异常,方便单元进行白盒测试
4.部分函数使用异常更好处理,如构造函数不方便使用错误码方式处理,比如T*operator[](size_t pos),如果pos越界,就只能使用异常或者错误码,不能使用返回值判断
缺点:
1,异常导致程序乱跳,并且是运行时出错就会跳,这会导致我们调试代码时,比较困难。
2,C++没有垃圾回收机制,资源要自己释放,有了异常会导致形成内存泄露,死锁等
3,C++标准库的异常体系定义的不好。
4,异常尽量规范使用,不规范,导致捕获的问题看不明白,所以异常规范有两点,第一,抛出异常类型来自于基类,第二,函数抛出的异常均使用throw的方式规范化。



