C++异常沉思录之基础篇

 

C++异常沉思录之基础篇

 

异常基本概念

异常即程序在运行期间产生的非预期性错误,如内存配额不足、文件不存在、网络不可用、SQL语句非法等。

异常分为可恢复性异常和不可恢复性异常。异常是否可恢复,不同的应用场景将有不同的理解。例如,对于内存配合不足,在短暂的峰涌情况下是可以快速恢复的,属于可恢复性异常,一但内存被释放和回收,程序将回到常态;而在内存长时间占用的情况下,则可以认为属于不可恢复性异常,此时应当终止程序的运行。

异常又可分为已知异常和未知异常。已知异常是指程序编写者事先考虑到的异常,对异常有清晰的定义和处理逻辑;未知异常是指程序编写者事先未察觉到的异常,可以理解为程序bug。

异常捕获是一把双刃剑,对异常进行显示捕获,尤其对于bug型的未知异常,需要找出产生异常的原因和代码进行修复,即需要考虑异常的分析定位机制。因此,对于未知异常或者无法处理的异常,不应当使用catch(…)笼统的进行捕获,当不需要关心异常或者无法处理异常的时候,就应该避免捕获异常或者将未处理的异常再次抛出,以便交由bugreport统一对未处理异常进行捕获、分析。

C++提供了强大的异常处理机制将异常产生和异常处理的代码分离,即将程序的正常业务逻辑和错误处理逻辑进行分离,并在不改变函数原型的情况下,实现异常对象的“向上”传递。

 

函数的异常声明

在函数的声明后面使用throw关键字可对函数的异常产生进行限制。

不允许函数抛出任何异常

void f() throw(); 表示f函数不允许抛出任何异常;以VS2005编译器为例,若在f函数中抛出异常,编译时会有如下提示:

warning C4297: 'f' : function assumed not to throw an exception but does

1>        __declspec(nothrow) or throw() was specified on the function

但异常还是能正常抛出和被捕获。

 

允许函数抛出任意异常

void f() throw(…);表示允许f函数抛出任意异常。

 

允许函数抛出指定类型的异常

void f() throw(MyException1,MyException2);表示允许函数f抛出MyException1和MyException2两种类型的异常。以vs2005为例,在f中抛出其他类型的异常,编译未见提示,且异常能正常抛出和被捕获。

 

异常的抛出

使用关键字throw抛出异常,有如下两种语法。

throw express;抛出express所代表的表达式的异常,异常类型为表达式的值的类型,异常的值为表达式的值。例如,

try{

throw 10;

}catch(int e){

}

throw 10;语句抛出int型异常,异常的值为10。catch(int e)语句能捕获int型异常,e的值就是异常表达式的值10。

 

throw;将异常继续“向上传递”。例如,

try{

throw 10;

}catch(int e){

         throw;

}

throw;语句将throw 10;语句产生的int型异常继续向上传递。

 

异常类型

任何类型都可以作为异常类型,包括基本数据类型,如int、double、char等;还包括类类型,如std:string;还包括自定义异常类型,即从std::exception派生的子类。

 

异常对象的传递

异常对象的传递和函数参数的传递一样,有值传递、指针传递和引用传递3种传递方式。

值传递会产生临时对象拷贝,且不支持多态。例如,

class MyException:public std::exception{

public: 

     MyException(const char* msg){

         msg_ = msg;

     }

     const char* what(){

         return msg_.c_str();

     }

protected:

     std::string msg_;

};

void func3() throw(MyException){

     throw MyException("func3 raised an exception.");

     return;

}

int _tmain(int argc, _TCHAR* argv[])

{

     try{

          func3();

     }catch(std::exception e){

         printf(e.what());

     }

     return 0;

}

///<运行结果:UnKnown exceptions。

 

因为值拷贝不支持动态联编,因此e.what()访问的是基类exception的what方法,输出的就是Unknown exceptions。

 

指针传递可以实现多态,若抛出临时对象的地址,容易出现悬挂指针错误;若在堆上分配异常对象,使用完后需要对其进行删除,一来删除对象的时机不确定,二来代码可读性、可维护性遭到破坏,违反了从哪里来回哪里去的内存分配释放原则。指针传递的示例代码如下所示,

class MyException:public std::exception{

public: 

     MyException(const char* msg){

         msg_ = msg;

     }

     const char* what(){

         return msg_.c_str();

     }

protected:

     std::string msg_;

};

void func3() throw(MyException){

     throw new MyException("func3 raised an exception.");

     return;

}

int _tmain(int argc, _TCHAR* argv[])

{

     try{

          func3();

     }catch(std::exception* e){

         printf(e->what());

         delete e;///<释放异常对象

     }

     return 0;

}

///<运行结果:func3 raised an exception.。

 

值引用传递既能避免临时对象的拷贝,又能支持多态,且没有对象释放问题。推荐采用值引用的方式传递异常对象。示例代码如下,

class MyException:public std::exception{

public: 

     MyException(const char* msg){

         msg_ = msg;

     }

     const char* what(){

         return msg_.c_str();

     }

protected:

     std::string msg_;

};

void func3() throw(MyException){

     throw MyException("func3 raised an exception.");

     return;

}

int _tmain(int argc, _TCHAR* argv[])

{

     try{

          func3();

     }catch(std::exception& e){

         printf(e.what());

     }

     return 0;

}

///<运行结果:func3 raised an exception.。

 

 

catch的匹配规则

异常匹配采用自底向上的匹配顺序进行匹配,即先在异常产生的函数中进行匹配,若该造成该异常的代码没有被try…catch…捕获或者catch类型不匹配,则向上传递直到匹配到第一条catch结束匹配,或则未发现任何匹配的catch则产生未处理异常交由运行时环境处理。

对于基本数据类型的异常,采用严格类型匹配机制,不支持类型转换。例如,

try{

throw float(1.6);

}catch(double e){

}

抛出float类型的异常,double类型的catch是匹配不成功的。

对于自定义类型,基类类型能够匹配子类异常对象,子类类型不能匹配基本异常对象。例如,

class MyException:public std::exception{

public: 

     MyException(const char* msg){

         msg_ = msg;

     }

     const char* what(){

         return msg_.c_str();

     }

protected:

     std::string msg_;

};

void func1() throw(){

     throw MyException ("MyException");

}

int _tmain(int argc, _TCHAR* argv[])

{

     try{

         func1();

     } catch(std::exception& e){ ///<使用基类类型匹配子类对象,匹配成功

         printf(e.what());

     } catch(MyException& e){

         printf(e.what());

     }

     return 0;

}

//运行结果:MyException

 

class MyException:public std::exception{

public: 

     MyException(const char* msg){

         msg_ = msg;

     }

     const char* what(){

         return msg_.c_str();

     }

protected:

     std::string msg_;

};

void func1() throw(){

     throw std::exception("std::exception");

}

int _tmain(int argc, _TCHAR* argv[])

{

     try{

         func1();

     }catch(MyException& e){///<使用子类类型匹配基类对象,匹配失败

         printf(e.what());

     }catch(std::exception& e){///<匹配成功

         printf(e.what());

     }

     return 0;

}

//运行结果:std::exception

 

异常调用栈unwind

拟另起文档专门进行描述。

 

供应宝异常体系结构

供应宝异常类受限于sens名字空间,其部分类图如下所示。

 

sens::exception

供应宝异常基类,派生自c++标准异常类std::exception,统一异常处理行为,如输出堆栈,输出日志等。

showstack()

输出异常产生时的堆栈,便于分析和定位异常。关于如何进行异常堆栈跟踪,将另起文档专门进行描述。

log()

将异常输出到日志文件中。

exception()

构造函数,调用showstack和log函数输出异常的调用堆栈和异常日志。

 

sens::dbexception

数据库异常类。

 

sens::fileexception

文件异常类

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值