c++中的异常

c语言中传统错误处理方式的
  • 终止程序
如使用assert来终止程序
缺陷: 难以接收,如果发生了内存错误程序就会终止如除0
  • 返回错误码:
缺陷:需要对应错误码查找对应的错误,许多库会将错误码方到erron中,表示错误
  • setjmp longjmp
int setjmp( jmp_buf env );
void longjmp( jmp_buf env, int value );  //可以跨函数跳转
 -  setjmp(j)设置“jump”点,用正确的程序上下文填充jmp_buf 对象j。这个上下文包括程序存放位置、栈和框架指针,其它重要的寄存器和内存数据。当初始化完jump 的上下文,setjmp()返回0 值。对setjmp函数的调用时,会保存程序当前的堆栈环境到env参数中;
 -  以后调用longjmp(j,r)的效果就是一个“长跳转”到由j 描述的上下文处(也就是到那原来设置j 的setjmp()处)。当作为长跳转的目标而被调用时,setjmp()返回r 或1(如果r 设为0 的话)。(记住,setjmp()不能在这种情况时返回0。
   通常, 用longjmp()来终止异常,用setjmp()标记相应的异常处理程序, 在调用setjmp的函数返回之前,调用longjmp,否则结果不可预料。
c++ 中的异常
  • 异常是一中处理错误的方式,当程序中出现无法处理的错误之时,就会抛出异常让函数的直接或间接的调用者处理这个函数
  • throw : 当程序出现异常时,会抛出异常通过throw关键字来完成
  • catch : catch 关键字用于捕捉异常 ,可以有多个catch来进行捕捉
  • try : try 块中的代码标识将被激活的特定异常,它后面通常跟着一个或多个 catch 块。

使用方法

try
{
// 保护的标识代码
}catch( ExceptionName e1 )
{
// catch 块
}catch( ExceptionName e2 )
{
// catch 块
}catch( ExceptionName eN )
{
// catch 块
}
异常的抛出和匹配的规则
  • 异常通过抛出的对象而引发,对象的类型决定了应该激活那个catch的处理代码
  • 被选中的处理代码是调用链中与该对象类型匹配且离抛出异常位置最近的那一个
  • 抛出异常对象后,会生成一个异常对象的拷贝,因为抛出的异常对象可能是一个临时对象,所以会生成一个拷贝对象,这个拷贝的临时对象会在被catch以后销毁。(这里的处理类似于函数的传值返回)
  • catch(…)可以捕获任意类型的异常,问题是不知道异常错误是什么
  • 实际中抛出和捕获的匹配原则有个例外,并不都是类型完全匹配,可以抛出的派生类对象,使用基类捕获。
函数调用链中异常栈展开匹配规则
  • 首先检查throw本身是否在try块内部,如果是再查找匹配的catch语句。如果有匹配的,则调到catch的地方进行处理
  • 没有匹配的catch则退出当前函数栈,继续在调用函数的栈中进行查找匹配的catch。
  • 如果到达main函数的栈,依旧没有匹配的,则终止程序。
  • 实际中在最后都需要加一个catch(…)捕获任意类型的异常,否则当有异常没有被捕获时,程序就会终止
  • 找到匹配的catch子句并处理以后,会继续沿着catch子句后面继续执行
栈展开的异常的查找是在编译时进行查找,如果早运行时就会造成很大的开销
异常的重新抛出
double Division(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 << 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* errmsg)
{
cout << errmsg << endl;
}
return 0;
}
异常的安全
  • 构造函数完成对象的初始化,最好不要在构造函数中进行抛异常否则会造成构造的初始化不完整
  • 析构函数主要完成资源的清理,最好不要在析构函数内抛出异常,否则可能导致资源泄漏(内存泄漏、句
    柄未关闭等)

可能因为异常的抛出而导致的资源的泄漏问题

  • 在锁中间抛异常
  • 在文件open close 之间抛异常
  • 在new 与delete之间抛异常

可以用RAII智能指针进行处理

异常使用的规范
  • 异常规格说明的目的是为了让函数使用者知道该函数可能抛出的异常有哪些。 可以在函数的后面接
    throw(类型),列出这个函数可能抛掷的所有异常类型。
  • 函数的后面接throw(),表示函数不抛异常。
  • 若无异常接口声明,则此函数可以抛掷任何类型的异常
void fun() throw(A,B,C,D);
// 这里表示这个函数只会抛出bad_alloc的异常
void* operator new (std::size_t size) throw (std::bad_alloc);
// 这里表示这个函数不会抛出异常
void* operator delete (std::size_t size, void* ptr) throw();
自定义的异常体系

在实际的使用中会定义一套的继承的规范体系,所抛出的以异常都是继承的派生类对象,可以使用一个基类来进行捕获。

// 服务器开发中通常使用的异常继承体系
class Exception
{
protected:
string _errmsg;
int _id;    //一般都会有一个错误编码和错误描述
//list<StackInfo> _traceStack;
// ...
};
class SqlException : public Exception
{};
class CacheException : public Exception
{};
class HttpServerException : public Exception
{};
int main()
{
try{
// server.Start();
// 抛出对象都是派生类对象
}
catch (const Exception& e) // 这里捕获父类对象就可以
{}
catch (...)
{
cout << "Unkown Exception" << endl;
}
return 0;
}
c++标准库的异常体系

c++ 标准库中提供了一个继承体系,一个父类exception

int main()
{
try{
vector<int> v(10, 5);
// 这里如果系统内存不够也会抛异常
v.reserve(10000000);
// 这里越界会抛异常
v.at(10) = 100;
}
catch (const exception& e) // 这里捕获父类对象就可以
{
cout << e.what() << endl;
}
catch (...)
{
cout << "Unkown Exception" << endl;
}
return 0;
}
异常的优缺点
优点:
  • 相比于错误码可以精准清晰的展示错误的各种信息,甚至可以包含堆栈调用的信息,可以更好的定位错误
  • 通过错误码返回错误,如果在函数的深度调用中发生了错误就需要一层一层的返回在最外层进行接收
缺点:
  • 异常会导致执行时跳转混乱,使跟踪调试分析时较为困难
  • 异常会有一些开销,但在如今硬件条件较好的情况下,可以忽略不记
  • 异常容易导致一些资源泄漏的的问题,如造成死锁的,内存泄漏,文件描述符泄漏,需要使用智能指针进行处理增加了学习的成本
  • c++异常体系设计不好,导致各自定义的异常体系较为混乱
  • 异常要规范使用,不能随意的抛异常

两个重要的异常规范:

  • 抛出的异常类型都接收自一个基类
  • 都使用fun() throw() 异常规范
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值