C++ 异常
-
C语言处理错误的方式
- 终止程序:如assert(), 缺陷:直接终止程序,执行流终止,无法接受.
- 返回错误码: 缺陷需要程序员自己去查找对应错误的处理方式,如系统中很多库函数的接口将接口函数都是通过将错误码放入error中,表示错误.
-
C++处理错误方式
异常机制:当函数发现自己无法处理的错误时,就可以抛出异常,让函数直接或间接的调用者处理这个问题.
throw:当程序出现问题时,程序会抛出异常,通过使用throw关键字来完成. catch:在想要处理问题的地方,通过异常捕获程序捕获异常,catch用于捕获异常,可以有多个catch进行捕获 try:try 块中的代码标识将被激活的特定异常,它后面通常跟着一个或多个 catch 块。
如果有一个块抛出异常,捕获异常的方法会使用try 和 catch关键字.try块中放可能抛出异常的代码.try块中代码被成为保护代码.
try{ //保护的表示代码 } catch(ExceptionName e1) { //catch块 } catch(ExceptionName e2) { //catch块 }
-
异常的使用
3.1异常的抛出和捕获
- 异常的抛出和捕获规则
- 异常是通过抛出对象而引发的,该对象的类型决定了应该激活哪个catch的处理代码。
- 被选中的处理代码是调用链中与该对象类型匹配且离抛出异常位置最近的那一个
- 抛出异常后,会生成一个异常的临时拷贝,因为抛出异常的对象可能是一个临时对象.所以会生成一个拷贝对象.这个拷贝的临时对象会在被catch后销毁.
- catch(…)可以捕获任何异常,但是不知道异常的错误是什么.
- 在实际抛出和捕获的匹配原则有个例外,并不都是类型完全匹配,可以抛出的派生类对象.用基类捕获,这个在实际中非常使用.
- 在函数调用链中异常栈展开原则
- 首先检查throw是否在try块内部,如果是在查找匹配的catch语句.如果有匹配的,跳转到catch的地方进行处理.没有匹配的catch则退出当前函数栈,继续在调用函数栈中查找匹配的catch
- 如果到达main函数的栈,没有匹配则终止程序.
- 找到匹配的catch并处理后,会继续沿着catch子句后面进行.
- 异常的抛出和捕获规则
[编程实验]
// 测试抛出 const char* 类型异常
int Division(int len, int time)
{
if (len == 0 || time == 0)
{
throw"Division by zero condtion"; //抛出 const char* 类型异常
}
else
return (double)len / (double)time;
cout << "Division 函数继续执行" << endl;
}
//测试抛出 int 类型异常
void function(int len, int time)
{
if (len % 2 == 0 || time % 2 == 0)
throw 1; //抛出 int 类型异常
cout << "function 函数继续执行" << endl;
}
void Func()
{
int len, time;
cin >> len >> time;
cout << Division(len, time) << endl;
function(len, time);
}
int main()
{
try {
Func();
}
catch(const char* errmes) //对 const char * 类型异常捕获
{
cout << __LINE__ <<"行, 捕获const char*类型: " << errmes << endl;
}
catch (int x) //对 int 类型异常捕获
{
cout << __LINE__ << "行, 捕获int类型: " << x << endl;
}
catch (...)
{
cout<< __LINE__ << "行, 捕获任何类型异常 "<< endl;
}
cout << "程序正常执行" << endl;
cout << "程序正常执行" << endl;
return 0;
}
//第一次实验:
输入: 10 20
输出:
0
39行, 捕获int类型: 1
程序正常执行
//第二次实验
输入:0 20
输出:
35行, 捕获const char*类型: Division by zero condtion
程序正常执行
//测试异常捕获就近原则
输入:10 20
0
39行, 捕获int类型: 1
程序正常执行
3.2 异常的重新抛出
有可能单个的catch不能完全处理一个异常,在进行了依稀校验后,希望在交给更外层调用函数链处理,catch可以通过重新抛出异常传递给更上层进行处理.
[编程实验]
int Division(int len, int time)
{
//当b == 0时出异常
if (len == 0)
{
throw"Division by zero condtion";
}
return (double)len / (double)time;
}
void Func()
{
//这里可以看到如果发生除零错误抛出异常,另外下面的array没有得到释放,不处理异常会导致内存泄漏
//所以这里捕获异常后并不处理异常,异常还是交给外边处理,这里捕获了异常后重新抛出去
int* array = new int[10];
try {
int len, time;
cin >> len >> time;
cout << Division(len, time) << endl;
}
catch (...)
{
cout << "delete[]" << array << endl;
delete[] array;
throw;
}
//....
cout << "delete[]" << array << endl;
delete[]array;
}
int main()
{
try {
Func();
}
catch (const char* message)
{
cout << message << std::endl;
}
return 0;
}
//结果:
输入:0 20
delete[]000002AACCD71A60
Division by zero condtion
输入:10 20
0
delete[]00000227A4614560
3.3 异常安全
-
构造函数完成对象的构造好和初始化,最好不要在构造函数中抛出异常,否则可能导致对象不完成或没有完全初始化.
-
析构函数主要是完成资源的清理,最好不要在析构函数内抛出异常,否则可能导致资源的泄漏.
-
C++中经常会导致资源泄露,如在new 和delete之间抛出了异常,导致内存泄漏,在lock 和 unlock之间抛出了异常导致死锁,C++经常使用RAIIl来解决以上问题.
[编程实验]
//在构造函数中抛出异常 class A { public: A() { cout << "A()" << endl; _ptr1 = new int; //这里抛出异常,导致_ptr2未初始化,析构时会删除空指针 /*int x, y; cin >> x >> y; Division(x, y);*/ _ptr2 = new int; } ~A() { cout << "~A()" << endl; delete _ptr1; //这里抛出异常,会导致未删除_ptr2,内存泄漏 /*int x, y; cin >> x >> y; Division(x, y);*/ delete _ptr2; } private: int* _ptr1; int* _ptr2; };
- 自定义异常体系
-
实际上很多公司都会自定义自己的异常体系,进行规范的异常管理,因为在项目中如果大家随意抛异常,那么外层的调用者基本使用不了.
[编程实验]
//在服务器中通常使用的异常体系
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)
{}
protected:
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 std::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()
{
srand(time(0));
if (rand() % 7 == 0)
{
throw SqlException("权限不足", 100, "select * from name = '张三'");
}
//throw "xxxxxx";
}
void CacheMgr()
{
srand(time(0));
if (rand() % 5 == 0)
{
throw CacheException("权限不足", 100);
}
else if (rand() % 6 == 0)
{
throw CacheException("数据不存在", 101);
}
SQLMgr();
}
void HttpServer()
{
// ...
srand(time(0));
if (rand() % 3 == 0)
{
throw HttpServerException("请求资源不存在", 100, "get");
}
else if (rand() % 4 == 0)
{
throw HttpServerException("权限不足", 101, "post");
}
CacheMgr();
}
int main()
{
srand(time(0));
// 20:15继续
while (1)
{
Sleep(1000);
try {
HttpServer(); // io
}
catch (const Exception& e) // 这里捕获父类对象就可以
{
// 多态
cout << e.what() << endl;
}
catch (...)
{
cout << "Unkown Exception" << endl;
}
}
return 0;
}
- 异常的优缺点
优点:
- 异常对象定义好了,相比于错误码可以清晰的展示出错误的各种信息,甚至包括调用堆栈,可以更好的定位程序的bug.
- 返回错误码的传统方式有很大的问题,在函数调用中,深层的函数返回了错误,那么需要层层返回错误,最外层才能拿到错误.
缺点:
-
异常导致程序执行流混乱,并且是运行出错程序就会乱跳,这会导致跟踪调试程序出现困难.
-
没有垃圾回收机制,资源需要自己来管理,有了异常非常容易导致内存泄漏,死锁等异常问题.
[完结!!!]