C++异常机制,一篇文章让你了解异常

C++异常机制

异常语法

一.异常抛出

•throw 异常对象;

•可以抛出基本类型的对象
–throw -1;
–throw “打开文件失败!”;

•可以抛出类类型的对象
–FileException ex (“打开文件失败!”);
throw ex;
–throw FileException (“打开文件失败!”);

•不要抛出局部对象的指针

–FileException ex (“打开文件失败!”);
throw &ex; // 错误!

二.异常捕获
•try
{
可能引发异常的语句;
}
catch (异常类型1& ex)
{ 针对异常类型1的异常处理; }
catch (异常类型2& ex)
{ 针对异常类型2的异常处理; }
catch (…) { 针对其它异常类型的异常处理; }

•根据异常对象的类型自上至下顺序匹配,而非最优匹配,因此对子类类型异常的捕获不要放在对基类类型异常的捕获后面

    –class GeneralException { ... }; 
     class FileException : public GeneralException { ... };
    –try { ... } 
     catch (FileException& ex) 
     { ... } 
     catch (GeneralException& ex) 
     { ... }

•建议在catch子句中使用引用接收异常对象,避免因为拷贝构造带来性能损失,或引发新的异常
–try { … }
catch (FileException& ex) { … }

三.异常对象

•为每一种异常情况定义一种异常类型
–class FileException { … };
–class MemoryException { … };
–class SocketException { … };

•推荐以匿名临时对象的形式抛出异常
–throw FileException (…);

•异常对象必须允许被拷贝构造和析构

•建议从标准库异常中派生自己的异常

–#include <stdexcept>
–class FileException : public exception { ... };

•当执行一个throw语句时,运行期异常处理机制会把异常对象复制到一个临时对象中,该临时对象存在于某个“安全区”中

•存储临时异常对象的“安全区”高度平台相关,但可以保证在最后一个使用该异常对象的catch块完成之前,该对象都一直保持可用状态

•当异常发生时,没有什么地方是绝对安全的,除了这个存放临时异常对象的“安全区”,这里是唯一风平浪静的风暴之眼

•由此可见,将一个对象的指针作为异常抛出是不明智的,而通过引用访问“安全区”中的异常对象则是安全的

代码流程

一.正常流程

•两个执行
–函数中throw语句之后的代码执行
–try块中函数调用语句之后的代码执行
•两个不执行
–throw语句不执行
–catch块不执行
这里写图片描述
二.异常流程
•两个不执行
–函数中throw语句之后的代码不执行
–try块中函数调用语句之后的代码不执行

•两个执行
–throw语句执行
–catch块执行

异常说明

一.异常说明

•异常说明是函数原型的一部分,旨在说明函数可能抛出的异常类型

–返回类型 函数名 (形参表) throw (异常类型1, 异常类型2, ...) 
{ 函数体; }

–void connectServer (char const* config) throw (int, string, FileException) 
 { ... }

•异常说明是一种承诺,承诺函数不会抛出异常说明以外的异常类型
–如果函数抛出了异常说明以外的异常类型,那么该异常将无法被捕获,并导致进程中止

•隐式抛出异常的函数也可以列出它的异常说明

void connectServer (char const* config) throw (int, string, FileException);
–void login (char const* username, char const* passwd) throw (int, string, FileExcetion) { connectServer ("/etc/server.cfg"); }

•没有异常说明,表示可能抛出任何类型的异常
–void connectServer (char const* config);

•异常说明为空,表示不会抛出任何类型的异常
–void connectServer (char const* config) throw ();

如果基类中的虚函数带有异常说明,那么该函数在子类中的覆盖版本不能说明比其基类版本抛出更多的异常

class A 
 { 
    virtual void foo (void) throw (int, string); 
    virtual void bar (void) throw (); 
 };

–class B : public A 
 { 
    void foo (void) throw (double); // 错误 
    void bar (void) throw (); 
 };

•异常说明在函数的声明和定义中必须保持严格一致,否则将导致编译错误

构造函数与异常

一.构造函数抛出异常

•构造函数可以抛出异常,某些时候还必须抛出异常

–构造过程中可能遇到各种错误,比如内存分配失败
–构造函数没有返回值,无法通过返回值通知调用者

–class String 
 { 
    public: String (char const* str) 
    { 
        if (! (m_str = (char*)malloc (strlen (str ? str : "") + 1))) 
            throw MemoryException (strerror (errno)); ... } 
     };

–try 
 { String str (...); ... }
 catch (MemoryException& ex) 
 { ... }

二.不完整对象的回滚

•构造函数抛出异常,对象将被不完整构造,而一个被不完整构造的对象,其析构函数永远不会被执行
–构造函数的回滚机制,可以保证所有成员子对象和基类子对象,在抛出异常的过程中,都能得到正确地析构
–所有动态分配的资源,必须在抛出异常之前,手动释放,否则将形成内存泄漏,除非使用了智能指针
–if (抛出异常的条件满足)
{
释放此前动态分配的资源;
throw 异常对象;
}

析构函数与异常

一.析构函数抛出异常
•不要从析构函数中主动抛出异常
–在两种情况下,析构函数会被调用
1.正常销毁对象,离开作用域或显式delete
2.在异常传递的堆栈辗转开解(stack-unwinding)过程中,由异常处理系统销毁对象
–对于第二种情况,异常正处于激活状态,而析构函数又抛出了异常,并试图将流程移至析构函数之外,这时C++将通过std::terminate()函数,令进程中止

二.尽量内部消化异常
•在析构函数中,执行任何可能引发异常的操作,都尽量把异常在内部消化掉,防止其从析构函数中被继续抛出

–Dummy::~Dummy (void) 
 { 
    try { ... } 
    catch (FileException& ex) 
    { ... } 
    catch (MemoryException& ex) 
    { ... } 
    catch (GeneralException& ex) 
    { ... } 
    catch (runtime_error& ex) 
    { ... } 
    catch (exception& ex) 
    { ... } 
    catch (...) 
    {} 
 }

标准异常

•标准C++库中预定义的异常类型
这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值