在学习C语言时我们的程序出错的处理方式基本都是以下几种:
1.终止程序 (除数为0)
2.返回一个表示错误的值,附加错误码(errno)
3.返回一个合法的值,让程序处于一种非法的状态(atoi())
4.调用预先准备好的错误处理函数 (回调函数)
5.直接终止程序: abort 或者 exit().
6.setjmp() 和 longjmp() 函数组合。
C语言的常见处理方式之一:输出错误码,提示错误信息。
FILE* fp = fopen("test.txt", "r"); if (NULL == fp) { cout << errno << endl; cout << "open failed" << endl; } else { cout << "open!" << endl; }
而对C++这样的面向对象的语言来说,在开发大型的项目时,往往需要具有更严格的正常运转时间以及更健壮的错误检测和错误处理。错误处理经常必须跨越独立开发的多个子系统进行。而C语言
的错误处理机制就不能满足我们的需求了.
异常处理:异常,当一个函数发现自己无法处理的错误时抛出异常,让函数的调用者直接或
间接的处理这个问题。
异常的抛出和捕获:
1. 异常是通过抛出对象而引发的,该对象的类型决定了应该激活哪个处理代码。
2. 被选中的处理代码是调用链中与该对象类型匹配且离抛出异常位置最近的那一个。
3. 抛出异常后会释放局部存储对象,所以被抛出的对象也就还给系统了,throw表达式会初始化一个抛出特殊的异常对象副本(匿名对象),异常对象由编译管理,异常对象在传给对应的catch处理之后撤销。
try { //char* p = new char[0x7fffffff]; vector<int> v; //v[0]; //内部实现使用的是assert v.at(0); //抛异常 } catch (exception& e) //多态 (exception e) { cout << e.what() << endl; //what是虚函数 }栈展开:
出异常的时候,将暂停当前函数的执行,开始查找对应的匹配catch子句。首先检查throw本身是否在try块内部,如果是再查找匹配的catch语句。如果有匹配的,则处理。没有则退出当前函数栈,继续在调用函数的栈中进行查找,不断重复上述过程,若到达main函数的栈,依旧没有匹配的,则终止程序。上述这个沿着调用链查找匹配的catch子句的过程称为栈展开。找到匹配的catch子句并处理以后,会继续沿着catch子句后面继续执行。
void func2() { vector<int> v; for (size_t i = 0; i <= v.size(); ++i) { v.at(i); } cout << "func2()异常抛出后" << endl; } void func1() { try { func2(); } catch (exception& e) { cout << e.what()<< endl; cout << "func1() 捕捉e异常" << endl; } catch (int errid) { cout << errid << endl; cout << "func1() 捕捉errid异常" << endl; } cout << "func1()捕捉异常结束" << endl; int err = 10; throw err; } int main() { try { //int errid = 0; //throw errid; func1(); } catch (exception& e) { cout << e.what() << endl; cout << "main()中捕捉e异常" << endl; } catch (int errid) { cout << errid << endl; cout << "main()中捕捉errid异常" << endl; } cout << "main() 异常捕捉结束" << endl; system("pause"); return 0; }
异常的匹配规则:异常对象的类型与catch说明符的类型必须完全匹配。
几个例外:
1.允许从非 const 到 const 的转换。也就是说,非 const 对象的 throw 可以与指定接受 const 引用的 catch 匹配。
2.允许从派生类型型到基类类型的转换。
3.将数组转换为指向数组类型的指针,将函数转换为指向函数类型的适当指针。
在查找匹配 catch 的时候,不允许其他转换。具体而言,既不允许标准算术
转换,也不允许为类类型定义的转换。
异常重新抛出:
有可能单个的catch不能完全处理一个异常,在进行一些校正处理以后,希望再交给更外层的调用链函数来处理,catch则可以通过重新抛出将异常传递给更上层的函数进行处理.
void test1() { FILE* fp = fopen("test.txt", "r"); if (NULL == fp) { throw 1; } fclose(fp); } void test2() { int* p = new int[10]; try { test1(); } catch (...) //对所有的异常捕获 { delete[] p; cout << "test2" << endl; throw; //将异常二次抛出 } delete[]p; } int main() { try { test2(); } catch (int err) { cout << err << endl; cout << "main" << endl; } system("pause"); return 0; }异常规范:在函数声明之后,列出该函数可能抛出异常类型,并保证该函数不会抛出其他类型的异常。
注意:
1.成员函数在类内声明和类外定义两处必须有相同的异常规范函数抛出一个没有被列在它异常规范中的异常时(且函数中抛出异常没有在函数内部进行处理),系统调用C++标准库中定义的函数unexpected()
2.如果异常规范为throw(),则表示不得抛出任何异常,该函数不用放在try块中
3.派生类的虚函数的异常规范必须与基类虚函数的异常规范一样或更严格(是基类虚函数的异常的子集)。因为:派生类的虚函数被指向基类类型的指针调用时,保证不会违背基类成员函数的异常规范。
异常与构造函数和析构函数:
注意:最好不要在构造函数和析构函数中抛异常。
1.构造函数完成对象的构造和初始化,需要保证不要在构造函数中抛出异常,否则可能导致对象不完整或没有完全初始化.
2.析构函数主要完成资源的清理,需要保证不要在析构函数内抛出异常,否则可能导致资源泄漏(内存泄漏、句柄未关闭等).
异常的优缺点:
优点:
1.比返回错误码的传统方式更清晰的表示程序错误
2.很多C++的第三方库使用异常,这样能更好的使用这些库
3.引入异常能更好的测试代码,因为如测试框架使用异常
缺点:
1.执行流乱跳,不方便调试代码
2.异常安的问题(内存泄漏等),需要更好的使用RAII编写异常安全代码,如智能指针。
3.异常会导致性能下降
异常处理在实际中应用实例:
#include <iostream> #include <vector> #include <string> #include <time.h> #include <windows.h> using namespace std; class Exception //异常类(基类) { public: Exception(const string& errmsg, int errid) :_errmsg(errmsg) , _errid(errid) {} virtual string What() { return _errmsg; } int GetErrId() { return _errid; } protected: string _errmsg; int _errid; }; class SqlException : public Exception //数据库层 { public: SqlException(const string& errmsg, int errid) :Exception(errmsg, errid) {} virtual string What() { string sqlerrmsg = "数据库层错误:"; sqlerrmsg += _errmsg; return sqlerrmsg; } }; class NetWorkException : public Exception //网络层 { public: NetWorkException(const string& errmsg, int errid) :Exception(errmsg, errid) {} }; class CacheException : public Exception //缓存层 { public: CacheException(const string& errmsg, int errid) :Exception(errmsg, errid) {} virtual string What() { string cacheerrmsg = "缓存层错误:"; cacheerrmsg += _errmsg; return cacheerrmsg; } }; void ConnectSql() { if (rand() % 7 == 0) { throw SqlException("连接数据库失败", 1); } } void InitCache() { if (rand() % 11 == 0) { throw CacheException("初始化缓存失败", 1); } } class NetWorkServer { public: void StartSqlMgr() { ConnectSql(); } void StartCacheMgr() { InitCache(); } void Start() { while (1) { try { StartSqlMgr(); StartCacheMgr(); Sleep(500); } catch (Exception& e) { cout << e.What() << endl; } catch (...) { cout << "未知异常" << endl; } } } }; void Send() { int* p1 = new int[1024]; try { if (rand() % 7 == 0) { throw SqlException("好友数据库中没有对方好友信息", 1); } if (rand() % 3 == 0) { throw NetWorkException("连接服务器失败", 2); } } catch (...) { delete[] p1; throw; // 异常的重新抛出 } delete[] p1; } void MySend() { try { Send(); } catch (Exception& e) { // log(e); if (e.GetErrId() == 1) { throw Exception("你已经不是对方的好友", 1); } else if (e.GetErrId() == 2) { throw Exception("您的网络不稳定,请检查网络", 2); } } } int main() { srand((unsigned int)time(0)); /*NetWorkServer s; s.Start(); */ while (1) { try { MySend(); Sleep(100); } catch (Exception& e) { cout << e.What() << endl; } catch (exception& e) { cout << e.what() << endl; } } return 0; }