异常的概念
异常处理机制允许程序中独立开发的部分在运行时就出现的问题进行通信并做出相应的处理,异常使得我们将问题的检测与解决问题的过程分开,程序的一部分负责检测问题的出现,然后解决问题的任务传递给程序的另一部分,检测环节问题处理模块的不需要所有细节。
C语言主要通过错误码的形式处理错误,错误码本质就是对错误信息进行分类编号,拿到错误码以后还要去查询错误信息,比较麻烦。
异常时抛出一个对象,这个对象可以包含更全面的各种信息。
异常的抛出和捕获
程序出现问题时,我们通过抛出(throw)一个对象来引发异常,该对象当前的类型以及调用链决定了应该由哪个catch的处理代码来处理该异常。
被选中的处理代码是调用链中与该对象类型匹配且离抛出异常最近的哪一个。根据抛出对象的类型和内容,程序抛出异常部分告知异常处理部分到底发生了什么错误。
当throw执行时,throw后面的语句将不再执行。程序的执行从throw位置跳到与之匹配的catch模块,catch可能是同一个函数的一个局部的catch,也可能是调用链上另一个函数的catch,控制权从throw转移到catch位置。含义:沿着调用链的的函数可能提早退出。2一旦程序开始执行异常处理程序,沿着调用链创建的对象都将销毁。
抛出异常对象后会生成一个异常对象的拷贝,因为抛出的异常对象可能是一个局部对象,所以会生成一个拷贝对象,这个拷贝对象会在catch子句后销毁。
栈展开
抛出异常后,程序暂停当前函数的执行,开始寻找与之匹配的catch子句,首先检查throw是否在try块内部,如果在查找与之匹配的catch语句,如果有匹配的则跳到catch的地方处理。
如果当前函数没有try/catch子句,或者有try/catch子句但是类型不匹配,则退出当前函数,继续在外层调用函数链中查找,这个过程就叫做栈展开。
如果到达main函数,依旧没有找到匹配的catch子句,程序会调用terminate函数终止程序。
如果找到匹配的catch子句处理后,catch子句代码会继续执行。
double Divild(int a, int b) { try { if (b == 0) { string s("Divide by zero condition!"); throw s; } } catch(int errid) { cout << errid << endl; } return (double)a / (double)b; } void Func() { int left, right; cin >> left >> right; try { cout << Divild(left, right) << endl; } catch(const string& errmsg) { cout <<"Func()->" << errmsg << endl; } cout << __FUNCTION__ << ":" << __LINE__ << "⾏执⾏" << endl; } int main() { while (1) { try { Func(); } catch (const string& errmsg) { cout << "main->" << errmsg << endl; } } return 0; }查找匹配的处理代码
一般情况下抛出的对象和catch是完全匹配的,如果有多个类型匹配,就选择离它位置最近的那个。
例外,允许从非常量向向量的类型转换(权限缩小),允许数组转换成指向数组元素类型的指针,函数被转换成指向函数的指针;允许派生类对象向基类类型的转换,继承体系用这个实现。
如果到main函数,异常仍旧没有被匹配就会终止程序,不发生严重错误,我们是不希望程序终止,所以在main函数最后都会使用 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 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: //string _errmsg; //错误信息 //int _id; //错误编码 const string _type;//错误类型 }; 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: //string _errmsg; //错误信息 //int _id; //错误编码 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; } }; 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(); } #include<thread> // 每个模块的继承都是Exception的派⽣类,每个模块可以添加⾃⼰的数据 // 最后捕获时,我们捕获基类就可以 //int main() //{ // srand(time(nullptr)); // while (1) // { // this_thread::sleep_for(chrono::seconds(1)); // // try // { // HttpServer(); // } // catch (const Exception& e) // { // cout << e.what() << endl; // } // catch (...) // { // cout << "unknowed error" << endl; // } // } // return 0; //}异常重新抛出
有时catch到一个异常对象后,需要对错误进行分类,其中的某种错误需要进行特殊处理,其他错误则重新抛出异常给外层的调用链处理。捕获异常后重新抛出,直接throw;就可以把捕获的异常重新抛出。
void _Sendmsg(const string& str) { if (rand() % 2 == 0) { throw HttpException("网络不稳定,发送失败", 102, "put"); } else if (rand() % 7 == 0) { throw HttpException("你已经不是对方好友,发送失败", 103, "put"); } else { cout << "发送成功" << endl; } } void Sendmsg(const string& str) { for (int i = 0; i < 4; i++) { try { _Sendmsg(str); } catch (const Exception& e) { if (e.getid() == 102) { if (i == 3) { cout << "*****************" << endl; throw; } cout << "开始第" << i + 1 << endl; } else { throw; } } } } //int main() //{ // srand(time(nullptr)); // // string str; // while (1) // { // // this_thread::sleep_for(chrono::seconds(1)); // // try // { // Sendmsg(str); // } // catch (const Exception& e) // { // cout << e.what() << endl << endl; // } // catch (...) // { // cout << "unknowed error" << endl; // } // } // return 0; //}异常的安全问题
异常抛出以后,后面的代码就不再执行,前面申请了资源(内存,锁),后面进行释放,但是中间抛异常就会导致资源没有释放,引发资源泄露,产生安全性问题。中间我们需要捕获异常,释放资源后再重新抛出。(智能指针才是最优解)
其次析构函数,如果抛出异常也要小心,比如析构函数要释放10个资源,释放到第5个时抛出异常,则也需要捕获处理,否则后面5个资源就没释放,也资源泄露了。
/////////////////////////////异常安全 double Divide(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 << Divide(len, time) << endl; } catch (...) { // 捕获异常释放内存 cout << "delete []" << array << endl; delete[] array; throw; // 异常重新抛出,捕获到什么抛出什么 } cout << "delete []" << array << endl; delete[] array; } int main() { try { Func(); } catch (const char* errmsg) { cout << errmsg << endl; } catch (const exception& e) { cout << e.what() << endl; } catch (...) { cout << "Unkown Exception" << endl; } return 0; }异常规范
C++11在函数参数列表后面➕noexcept表示不会抛出异常,啥都不加表示可能会抛出异常。
编译器并不会在编译时检查noexcept,一个函数用noexcept修饰了以后,同时又包含了throw语句或者调用的函数可能会抛出异常,编译器还是会顺利通过。但是⼀个声明了noexcept的函数抛出了异常,程序会调⽤ terminate 终⽌程序。
noexcept(表达式)还可以作为一个运算符去检测一个表达式释放会抛出异常,可能则返回false,不会就返回true。
///////////////异常规范/////////////////////// double Divide(int a, int b) { // 当b == 0时抛出异常 if (b == 0) { throw "Division by zero condition!"; } return (double)a / (double)b; } int main() { try { int len, time; cin >> len >> time; cout << Divide(len, time) << endl; } catch (const char* errmsg) { cout << errmsg << endl; } catch (...) { cout << "Unkown Exception" << endl; } int i = 0; cout << noexcept(Divide(1, 2)) << endl; cout << noexcept(Divide(1, 0)) << endl; cout << noexcept(++i) << endl; return 0; }