传统的解决异常方式有两种:①终止程序,比如assert,一旦发生内存错误,直接终止程序②返回错误码,很多系统库的接口都是通过错误码反应错误,需要程序员自己去查找对应的错误
异常是一种处理错误的方式。当一个函数发现自己无法处理的错误时,就可以使用异常,让函数直接或间接处理该错误
异常相关关键字
throw:通过该关键字当问题出现时,程序会主动抛出一个异常
catch:用于捕获异常,可以有多个catch进行捕获。在需要处理问题的位置,通过异常处理程序捕获异常
try:try块中的标识将被激活的特定异常,后面通常跟一个或多个catch块
#include
#include
using namespace std;
double Division(int a, int b)
{
if (b == 0)
{
throw "除零错误";
}
else
{
return ((double)a / (double)b);
}
}
void Func()
{
int len, time;
cin >> len >> time;
cout << "void Func()" << endl;
}
int main()
{
try
{
Func();
}
catch(const char* str)
{
cout << str << endl;
}
return 0;
}
1.异常是通过抛出对象引发的,该对象的类型决定了应该激活哪个catch块。即throw后跟着是什么类型,catch()的参数就对应是什么类型
2.被选中的处理代码是调用链中与该对象匹配且离抛出异常位置最近的
3.catch(...)可以用于捕获任意类型的异常,但是不能获取具体的异常错误
4.实际中抛出和捕获的匹配原则有例外,即并不是所有抛出和捕获都是类型完全匹配的。可以抛出派生类对象,使用基类捕获
1.首先检查throw是否在try块内,如果在块内则查找匹配的catch语句。如果有匹配的,则从throw跳转到匹配catch处处理
2.没有匹配的catch则退出当前函数栈,继续在调用函数的栈中查找匹配的catch
3.如果到达main函数,仍然没有匹配的catch,则终止程序
4.找到匹配的catch子句并处理后,将会沿着catch子句后面继续执行
上面所说的,沿着调用链查找匹配的catch子句的过程叫做栈展开。所及在实际使用中一般都在最后增加catch(...)捕获任意类型的异常,否则当有异常没有捕获时,程序就会终止
#include
using namespace std;
double Division(int a, int b)
{
if (b == 0)
{
throw "除零错误";
}
else
{
return ((double)a / (double)b);
}
}
void Func()
{
int* array = new int[10];
try
{
int len, time;
cin >> len >> time;
cout << Division(len, time) << endl;
}
catch (...)//如果发生除0错误抛出异常,下面的array就得不到释放
{
cout << "delete[]\n" << array << endl;
delete[] array;
throw;//因此捕获到异常后并不处理异常,捕获后再重新抛出,交给主函数catch处理
}
cout << "delete[] array" << endl;
delete[] array;
}
int main()
{
try
{
Func();
}
catch (const char* str)
{
cout << str << endl;
}
return 0;
}
构造函数完成对象的构造和初始化,最好不要再构造函数中抛出异常,否则可能导致对象不完整或没有完全初始化
析构函数主要完成资源的清理,最好不要在析构函数中抛出异常,否则可能导致资源泄露
C++异常经常会导致资源泄露问题,比如new和delete中的异常抛出导致内存泄漏,在lock和unlock之间抛出异常导致死锁。C++一般使用RAII解决异常问题
异常规格说明的目的是让函数使用者知道该函数可能抛出的异常有哪些。可以在函数的后面接throw(类型),列出这个函数可能抛出的所有异常类型
函数的后面接throw(),标识该函数不抛出异常
如果没有异常接口声明,则该函数可以抛出任何类型的异常
C++11中新增了noexcept关键字,表示不会抛异常
void func()throw(A, B, C, D)//A、B、C、D为异常类型,可以是自定义类型
{
}
void* operator new(std::size_t size)throw(std::bad_alloc)//这个函数只会抛出bad_alloc异常
{
}
void fun()throw()//这个函数不会抛出异常
{
}
实际使用中很多公司都会自定义自己的异常体系进行规范的异常管理,因为一个项目中如果大家 随意抛异常,那么外层的调用者基本就没办法玩了,所以实际中都会定义一套继承的规范体系。 这样大家抛出的都是继承的派生类对象,捕获一个基类就可以了
#include
#include
#include
using namespace std;
class Exception
{
public:
Exception(int errid, const string& msg)
:_errid(errid),_errmsg(msg)
{
}
virtual string what()const
{
return _errmsg;
}
int GetErrid()const
{
return _errid;
}
protected:
int _errid;
string _errmsg;
};
class SqlException :public Exception
{
public:
SqlException(int errid,const string& msg,const string& sql)
:Exception(errid,msg),_sql(sql)
{}
virtual string what()const
{
string msg = "SqlException";
msg += _errmsg;
msg += "->";
msg += _sql;
return msg;
}
protected:
string _sql;
};
class CacheException :public Exception
{
public:
CacheException(const string& errmsg,int id)
:Exception(id,errmsg)
{}
virtual string what()const
{
string msg = "CacheException:";
msg += _errmsg;
return msg;
}
};
class HttpServerException :public Exception
{
public:
HttpServerException(const string& errmsg,int id,const string& type)
:Exception(id,errmsg),_type(type)
{}
virtual string what()const
{
string msg = "HttpServerException:";
msg += _errmsg;
msg += "->";
msg += _type;
return msg;
}
private:
const string _type;
};
void SQLMgr()
{
srand(time(0));
if (rand() % 7 == 0)
{
throw SqlException(100, "权限不足", "select * from name = '张三'");
}
cout << "调用成功" << endl;
}
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()
{
while (1)
{
this_thread::sleep_for(chrono::seconds(1));
try
{
HttpServer();
}
catch (const Exception& e) // 这里捕获父类对象就可以
{
// 多态
cout << e.what() << endl;
}
catch (...)
{
cout << "Unkown Exception" << endl;
}
}
return 0;
}
函数名称· | 函数功能 |
bad_alloc |
分配内存失败时引发异常,一般通过new抛出 |
bad_cast | 动态强制转换失败时引发异常,一般通过dynamic_cast抛出 |
bad_typeid | 空指针类型ID引发异常,一般通过typeid抛出 |
bad_exception | 意外处理程序引发异常 |
logic_error | 逻辑错误异常 |
runtime_error | 运行时错误异常 |
具体用法可以查看文档
优点:
①异常对象定义好后,相比较错误码可以更加清晰的显示错误的各种信息,还能包含堆栈信息,用于调试定位bug
②传统的返回错误码方式,可能导致多层返回才能获取错误信息
③很多第三方库都包括异常处理
④部分函数使用异常更好处理
缺点:
①异常可能会导致程序的执行流乱跳,并且是在运行阶段出错抛异常就会混乱。不利于跟踪调试分析程序
②异常会消耗一些性能的开销
③C++没有垃圾回收机制,需要自己管理资源。可能导致内存泄漏,死锁等问题
④异常尽量规范使用,否则后果不堪设想,随意抛异常,外层捕获的用户苦不堪言。所以异常 规范有两点:一、抛出异常类型都继承自一个基类。二、函数是否抛异常、抛什么异常,都 使用 func() throw();的方式规范化