c++ 异常处理

第一节、使用异常

一、语法规则和结构图示

try {
        try-fields
        throw exception-object;
}
catch (exception-[object | pointer | reference] e) {
        catch-fields
}
catch (...) {
        catch-fields
}

任何具体类都可能作为异常类被抛出,包括内部类型,更多的时候我们使用专门的异常类以管理异常类层次结构和附加额外的信息。

二、异常机制的意义
1)传统的错误处理总是需要用户定义其规则,以手动处理错误,用户没有一种明确的方式在需要捕捉错误的场合使用,应用程序将更为脆弱
2)未捕捉的异常可导致应用程序被运行时终止,传统的错误处理模式经常在错误得不到处理的时候仍“静静地”往下继续执行,直到资源被破坏,造成死锁及内存 溢出等一系列系统错误
3)通过捕捉、处理异常,应用程序员可依据附加的运行时信息确定应用程序的后续执行
4)对于传统的错误处理形式,同样的错误码等返回信息,在不同的上下文可能表达不同的含义,异常处理机制使用派生类型和辅助信息以明确异常的分类
5)抛出的异常并不代表“几乎不出现的”、“灾难性的”,而是过程拒绝了部分操作,并需要用户指示下一步动作

三、标准异常

主要的异常类有:运行时错误(区间、上溢、下溢)、分配失败、异常处理失败、逻辑错误。

第二节、异常类和异常处理机制

一、派生类的内存结构

为了验证包含虚函数表的类对象在内存中的布局,可以使用硬编码追踪其中的虚函数项目:

ClassType* obj_ptr = new ClassType; // 动态创建类对象
void* vtbl = *(void**)obj_ptr; // 取得对象的第一个项目,存储虚表地址
void* method = *(void**)vtbl; // 取出虚表的前 4 个字节,解释为过程地址
std::cout << method << std::endl;
__asm call method; // 调用过程
method = *((void**)vtbl + 1);
std::cout << method << std::endl;
__asm call method;

void* 表示类型为通用指针,void** 便是存储指针的地址,在 IA-32 处理器的保护模式下,指针类型意味着 32 位长度。
上述代码将产生下述结果:

00419ECE
ClassType::virtual_method
00419D39
ClassType::virtual_method1
Press any key to continue

进行上述说明,旨在易于进一步理解异常类等类层次结构中关于派生类对象向基类对象的切割,派生类指针 / 引用向基类指针 / 引用的偏移是如何发生的。

二、组合异常
多重继承应用在异常类中,称之为组合异常。

class custom_err
        : public file_err, public io_err;

由此定义,捕捉 file_err 类型或者 io_err 类型在编译时根据对象的基类 / 虚函数表起始偏移进行相应的切割(对应于对象转换)或偏移(对应于指针 / 类型转换)。

三、异常捕捉条件

向捕捉式抛出异常类,在类层次中具有以下对应关系。

四、异常处理机制及其异常类的行为特性
1)异常捕捉式允许使用 const 限定
2)异常捕捉式中使用 throw 重新抛出异常
3)派生类之间的转换,编译器自动完成数据对象、虚表偏移(多重继承的不同基类在虚表中可具有不同的偏移)。一个派生类的两个不相关基类型之间的转换,仅 重解释指针类型,没有意义
4)在将派生类对象传递到顺位以上的基类异常捕捉式后,顺位以下的派生类异常捕捉式不再被处理
5)在过程中抛出异常,仅当对象已构造才会被执行析构,将对象内部资源组织成类成员可以细化析构

五、对象构造、析构和资源分配和销毁的时候抛出的异常
1)对象构造中的异常
对象执行构造时抛出异常,则构造未完成,析构亦不会被调用。使用嵌入类对象维护资源,如内存、文件操作等,使用标准库的容器类、流操作对象和自动指针等。
尽管构造未完成,但是其包含的基类对象则在派生类构造之前已经构造,所以可被析构。
遵守以下原则,以为资源提供类对象封装:
        1、“资源申请即初始化”
        2、“无论何时,只要允许析构,资源就会被释放”

2)不应对可能出现在异常抛出——堆栈回退的情况下将被销毁的对象的析构函数中抛出异常,这会导致运行时终止
以下两种方式用于在析构中避免抛出异常:
        1、在析构函数中调用可能引发异常的过程,使用 catch(...) 捕捉所有未知的异常
        2、调用 std::uncaught_exception() 确定是否包含未处理的异常(处于堆栈回退中),若存在,则该析构可能是执行堆栈回退时被调用的,避免抛出异常

六、认为异常机制是一种控制结构
在算法的外层可以尝试使用借助异常机制来控制程序结构
1)从深层嵌套种抛出特定异常信息
2)从递归式抛出返回类型立即返回
3)异常机制不应在效率优先的场合作为控制程序结构的方式

第三节、异常表和异常回调处理

一、限制抛出的异常类型
在过程的声明格式之后使用 throw(...) 语句,声明可被抛出的异常类型,或者未指定任何类型,使用 throw() 以表示不抛出任何异常。或者指定 __declspec(nothrow) 编译器指令。
当违反规则的异常类型对象被抛出,编译器将发出警告信息。

void throwexceptions() throw(err1, err2)
{
        procedure-fields
        throw err1;
        throw err2;
        throw err3;
}

上述过程中 err3 未被作为建议的异常类型放置到异常表中,在部分 C++ 编译器实现中执行进程的强制终止,而不抛出该异常。
在 Visual C++ 7.1 的实现中,异常表只是在编译时提供警告信息。

声明和定义应该保持一致的异常表,但在 Visual C++ 实现中,这不是必须的。

二、虚函数的异常表
虚函数在派生类中的重载版本应该具有一致的或者更受限的 throw 异常表,基类虚函数不接受的异常抛出,派生类也不应当接受,反之则不然。

*函数指针
异常表更受限的函数指针不接受允许受限的异常抛出的函数地址,不受限的函数指针可接受任意受限的函数地址。

三、内存分配异常捕捉函数
内存分配异常捕捉函数类似 atexit() 函数,为在使用 new 执行内存分配而引发的异常抛出提供响应机制。

typedef void (__cdecl* new_handler)();

上述是自定义捕捉函数的类型。

new_handler set_new_handler(new_handler _Pnew) throw();

设置了 new_handler 之后,operator new 将不抛出异常,因此 new_handler 在必要的情况下必须抛出异常以获得外部处理。

void my_new_handler()
{
        handler-fields
        throw std::bad_alloc();
}

四、unexpected() 非预期的错误
当在过程中抛出了“异常表以外”的异常,unexpected() 函数将被进程调用,unexpected() 转为调用已设置的非预期异常抛出处理句柄。

typedef void (__cdecl* unexpected_handler)();

set_unexpected_handler(unexpected_handler);

在 Visual C++ 7.1 的实现中,并未对抛出“异常表外”的异常而调用非预期处理函数。并且对于“异常表”的使用,仅仅是在违反规则时在编译时给以警告信息。直接使用 unexpected() 来访问非预期异常抛出的回调函数。

非预期异常抛出回调可能不返回到调用者,在以下情况被终止执行:
1)抛出了异常表中定义的类型对象或者当直接访问 unexpected() 函数时,在回调中抛出了任意类型的异常
2)抛出 bad_exception 异常类对象
3)调用 terminate, abort 或者 exit(int) 函数

当应用程序执行,默认的 unexpected 回调函数将访问 terminate。

当 bad_exception 被放置到过程的异常表,unexpected 将抛出 bad_exception 代替终止应用程序或者调用非预期异常抛出的回调。

五、异常处理回调函数综述
1)set_terminate(terminate_handler)
强制终止前发生。
terminate() 默认调用 abort(),set_terminate() 可设置 exit() 等其他处理策略。
2)set_new_handler(new_handler)
std::bad_alloc 异常被抛出时发生。
3)set_unexpected(unexpected_handler)
非预期异常抛出时发生。
在异常表中加入 std::bad_exception,bad_exception 在抛出不在异常表中的异常类时由 unexpected() 抛出,且不再执行 terminate() 或者通过 set_unexpected() 设置的异常回调。
4)通过 uncaught_exception() 函数判断是否位于异常堆栈回退中
在异常堆栈回退中再次抛出异常将导致应用程序终止或者难于维护的异常捕捉条件,通过检查异常状态来避免。

六、异常的再抛出
在异常回退、处理阶段,如 catch 式或者 terminate, std::bad_alloc, unexpected 等异常回调中,使用 throw (注意,未包含后续的异常类的对象)将抛出一个当前异常,以便被上一级嵌套的异常捕捉式或者 catch 处理式中抛出时由后续捕捉式执行匹配。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值