C++中处理异常的过程是这样的:在执行程序发生异常,可以不在本函数中处理,而是抛出一个错误信息,把它传递给上一级的函数来解决,上一级解决不了,再传给其上一级,由其上一级处理。如此逐级上传,直到最高一级还无法处理的话,运行系统会自动调用系统函数terminate.
格式:
try
{
}
catch(...) //表示任何异常
{
}
如果在catch 中没有调用throw 再次抛出异常,说明该异常在catch中被吞掉,不会再传出调用该函数之外。
throw:
1.throw抛出的永远是对象的拷贝(包括普局部变量、引用,静态变量)。不管该对象离开作用域时是否被析构(堆上变量、静态变量);或者被抛出的对象不会被释放,也会进行拷贝操作,如抛出的对象是静态变量,或者是引用)。
因此和函数传参有区别,函数传引用参数时不需要拷贝。
2.当异常对象被拷贝时,拷贝操作是由对象的拷贝构造函数完成的。该拷贝构造函数是对象的静态类型(static type)所对应类的拷贝构造函数,而不是对象的动态类型(dynamic type)对应类的拷贝构造函数。
class Widget { ... };
class SpecialWidget: public Widget { ... };
void passAndThrowWidget()
{
SpecialWidget localSpecialWidget;
...
Widget& rw = localSpecialWidget; // rw 引用SpecialWidget
throw rw; //它抛出一个类型为Widget
// 的异常
}
这里抛出的异常对象是Widget,即使rw引用的是一个SpecialWidget。因为rw的静态类型(static
type)是Widget,而不是SpecialWidget。
3.
catch (Widget& w) // 捕获Widget异常
{
... // 处理异常
throw; // 重新抛出异常,让它
} // 继续传递
catch (Widget& w) // 捕获Widget异常
{
... // 处理异常
throw w; // 传递被捕获异常的
} // 拷贝
第一个块中重新抛出的是当前异常(current exception),无论它是什么类型。特别是如果这个异常开始就是做为SpecialWidget类型抛出的,那么第一个块中传递出去的还是SpecialWidget异常,即使w的静态类型(static type)是Widget。这是因为重新抛出异常时没有进行拷贝操作。第二个catch块重新抛出的是新异常,类型总是Widget,因为w的静态类型(static type)是Widget。一般来说,你应该用throw来重新抛出当前的异常,因为这样不会改变被传递出去的异常类型,而且更有效率,因为不用生成一个新拷贝。
个人觉得:throw最好抛出的是对象。
执行throw后,如果在当前函数没有执行catch进行捕捉的话,throw后面的代码就不会被执行,所以在要注意内存泄漏的问题。
就算上一次函数执行了catch捕捉异常,原来的throw之后的代码也不会执行。
所以
1.在构造函数中抛出了异常,该构造函数就不能完整执行完,对象构造不成功。
2.在析构函数中抛出异常,存在内存泄漏。
延伸:函数声明后面加throw()的作用
void fun() throw(); //表示fun函数不允许抛出任何异常,即fun函数是异常安全的。
void fun() throw(...); //表示fun函数可以抛出任何形式的异常。
void fun() throw(exceptionType); // 表示fun函数只能抛出exceptionType类型的异常。
catch
1.catch()块接收异常类型不进行隐式类型转换(除了以下2种情况),被调函数则可以进行(例:try抛出的int异常,不会被处理double异常的catch块捕获)。
a.第一种是继承类与基类间的转换
b.一个类型化指针(typed pointer)转变成无类型指针(untyped pointer),所以带有const void* 指针的catch子句能捕获任何类型的指针类型异常。
2.异常传递到 catch 子句中有三种方式:通过指针(by pointer),通过传值(by value)或通过引用(by reference),其中引用最好.
a.通过指针的方式,对静态异常变量catch中不需要delete,但堆上异常变量需要delete,因此较复杂。而且通过指针捕获异常也不符合C++语言本身的规范。
b.通过传值时,需要进行拷贝两次(离开作用域一次,catch接收一次),而且它会产生 slicing problem(切割问题),即派生类的异常对象被做为基类异常对象捕获时,那它的派生类行为就被切掉了(sliced off)。这样的sliced对象实际上是一个基类对象:它们没有派生类的数据成员,而且当本准备调用它们的虚拟函数时,系统解析后调用的却是基类对象的函数。
c.异常变量复制一次,避免了上述所有问题。
延伸:
四个标准的异常
bad_alloc(当operator new(参见条款M8)不能分配足够的内存时,被抛出),
bad_cast(当dynamic_cast针对一个引用(reference)操作失败时,被抛出),
bad_typeid(当dynamic_cast对空指针进行操作时,被抛出)
bad_exception(用于unexpected异常;参见条款M14)――都不是指向对象的指针。
CException是Microsoft基本类库中处理各种异常的基础库。
CException是一个抽象基类,不可构造一个CException对象.
其派生库及描述如下:
内存不够 | |
CNotSupportedException | 请求不支持的操作 |
CArchiveException | 文档指定异常 |
CFileException | 文件指定异常 |
CResourceException | Windows资源未找到或不可创建 |
COleException | OLE异常 |
CDBException | 数据库异常(即基于开放数据库连接的MFC数据库类出现异常) |
COleDispatchException | OLE发送(自动)异常 |
CUserException | 资源无法找到 |
CDaoException | 数据访问对象异常(即DAO类出现异常) |
CInternetException | Internet异常(即Internet类出现异常) |
1.可以在try{}中手动抛出异常throw/
2.可以使用多个catch来捕捉异常。
3.可以调用GetErrorMessage或ReportError来向用户报告异常的详细情况。
4.如果Catch关键字得到异常,则不会自动删除。需调用delete函数。
构造一个CException对象 | |
Delete | 删除一个CException对象 |
GetErrorMessage | 获取异常描述信息 |
ReportError | 在消息框中向用户报告一个错误信息 |
try
{
}
catch(CException *e) //
{
e->ReportError();
e->delete();
}
使用的场景有:原则是容易抛出异常地方,如
1.内存操作的,分配、释放、移动
2.序列化读写文件CArchive
3.使用第三方接口读写excel文件。
bad_alloc 是operator new不能满足内存分配请求时抛出的异常类型。
如
try
{
}
catch(bad_alloc &e) //
{
}
try
{
}
catch(std::bad_alloc &) //
{
}