目录
C语言处理错误的方式有哪些呢?assert断言;返回错误码errno,然后我们可以通过这个错误码查到错误信息(strerror(errno));我们可以自定义函数的返回值,如果有错误返回什么等等。同样抛异常就是C++处理错误的一种方式。
简单使用
首先我们认识抛异常要用到的三个关键字:throw try catch,我们来简单的写一个程序来用一下
double Division(int a, int b) { if (b == 0) throw"The divisor is zero"; else return ((double)a / (double)b); } void func() { int a = 0, b = 0; cin >> a >> b; cout << Division(a, b) << endl; } int main() { try { func(); } catch (const char* errmsg) { cout << errmsg << endl; } return 0; }
通过这个代码我们来说明一些语法及特性
throw可以抛出任意类型的对象,可以是内置类型,也可以是自定义类型。
try后边必须有catch,并且可以跟多个catch,同样catch上面必须要有try,但不能有多个try
抛出异常后必须要有catch接收,否则程序会异常终止
类型匹配才能捕捉到,不管外部有多少层,可以直接跳到catch,并且catch后继续向下执行
throw跳catch是跳到最近的类型匹配的catch,这个可以验证一下:我们可以在func函数中也进行捕获,如果类型匹配,就会在func中捕获,如果类型不匹配,就会到main函数中捕获
我们可以传引用捕获,就是说抛出异常对象后,会生成一个异常对象的拷贝(这里拷贝的产生可以是移动构造),因为异常对象会因为出了作用域而销毁,这个拷贝会在catch之后销毁。
我们上面说如果有到了main函数还没有捕获的异常,那么进程就会异常退出,所以为了避免这种情况,有一种catch方法可以捕获任意类型的异常
异常安全
就是说不要在一些地方抛出异常,否则会出错
比如说:不要在构造函数中抛异常,否则对象可能无法完全初始化,会造成访问野指针或内存泄漏的问题,比如这个类:
class A { public: A() { cout << "A()" << endl; a1 = new int(10); throw 111; a2 = new int (10); } ~A() { delete a1; delete a2; cout << "~A()" << endl; } private: int* a1; int* a2; };
如果构造函数中途抛异常,那么因为对象没有初始化完,它是不会调用析构函数的,就会造成内存泄漏
同理,析构函数也不要抛异常,有可能会造成内存泄漏
还有这样一种情况,如果在普通函数中间new之后delete之前抛出了异常,那么我们需要new之后就捕获异常并且delete,然后再将异常重新抛出,因为不这么干就无法释放内存了,比如
void func() { int* arr = new int[10]; try { int a = 0, b = 0; cin >> a >> b; cout << Division(a, b) << endl; } catch (...) { cout << "delete []" << endl; delete[]arr; throw; } cout << "delete []" << endl; delete[]arr; }
这样的话,无论是否出异常都不会造成内存泄漏
那如果有三个new呢?因为new本身也是会抛异常的,对于当前的我们只能这么写
void fxxx() { int* p1 = new int(10); int* p2 = nullptr; int* p3 = nullptr; try { p2 = new int(20); try { p3 = new int(30); } catch(...) { delete p1; delete p2; throw; } } catch (...) { delete p1; throw; } } int main() { try { fxxx(); } catch (...) { cout << "出现异常" << endl; } return 0; }
非常麻烦,所以在我们后面学了智能指针后就可以这样写,就可以非常简单了
class smartptr { public: smartptr(int*ptr) :_ptr(ptr){} ~smartptr() { delete[]_ptr; } private: int* _ptr; }; void fxxx() { smartptr sp1(new int[10]); smartptr sp2(new int[20]); smartptr sp3(new int[30]); }
异常规范
为了方便代码的可读性,我们可以在函数的后面加上这个函数会抛出了什么类型的异常,如果确定不抛出异常,那么可以加noexpect关键字,比如:
double Division(int a, int b)throw(string)//可以加多个类型 { if (b == 0) throw string("The divisor is zero"); else return ((double)a / (double)b); } void func1()throw()//表示不会抛出异常 { cout << "func1" << endl; } void func2() noexcept//表示不会抛出异常 { cout << "func2" << endl; }
当然这只是建议性的,有的内部抛了异常编译器也查不出来,并且只是会警告,所以现实意义并不大
异常优缺点
优点:
1.异常的对象定义好了,相比错误码的方式可以清晰准确的显示错误的各种信息,可以帮助更好的定位程序的bug
2.如果返回错误码的话,需要层层返回,但是异常可以直接跳多层,更方便
3.很多第三方库也使用异常,我们要使用库的话就要使用异常
4.有些函数只能返回特定的对象,无法返回错误码,比如T& at(size_t i);
缺点:
1.异常会导致程序的执行流乱跳,非常的混乱,这就导致我们在调试程序时非常困难
2.就像上面异常安全说的,容易出现内存泄漏的问题,我们就需要学习智能指针,就增加的学习成本
3.异常规范不是强制的,所以就会导致有人不遵守,有人随便抛异常,就会导致外层捕获的用户苦不堪言
实践中异常的使用机制
实践中一般定义一个基类,然后别的类去继承,抛出派生类的异常可以用基类捕获,这样对于我们的捕获错误就简单多了,下面是代码实例:
class Exception { public: Exception(const string&errmsg,int id) :_errmsg(errmsg) ,_id(id){} virtual string what()const { return _errmsg; } 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 HttpServerException :public Exception { public: HttpServerException(const string& errmsg, int id, const string& type) :Exception(errmsg, id) ,_type(type){} virtual string what()const { string str = "HttpServerException"; str += _type; str += "->"; str += _errmsg; return str; } private: const string _type; }; void SQLMgr() { if (rand() % 7 == 0) { throw sqlException("权限不足", 100, "select*from name='张三'"); } cout << "!!!!!!!!!!!!!!!!!!!运行成功" << endl; } void CacheMgr() { if (rand() % 5 == 0) { throw CacheException("权限不足", 100); } else if (rand() % 6 == 0) { throw CacheException("数据不存在", 101); } SQLMgr(); } void HttpServer() { if (rand() % 3 == 0) { throw HttpServerException("请求资源不存在", 100, "get"); } else if (rand() % 4 == 0) { throw HttpServerException("权限不足", 101, "post"); } CacheMgr(); } int main() { srand(time(0)); while (1) { Sleep(1000); try { HttpServer(); } catch (const Exception& e) { cout << e.what() << endl; } catch (...) { cout << "unkown Exception" << endl; } } return 0; }