设为首页 加入收藏

TOP

C++ Primer 学习笔记_87_用于大型程序的工具 --异常处理(一)
2015-07-24 07:26:39 来源: 作者: 【 】 浏览:267
Tags:Primer 学习 笔记 _87_ 用于 大型 程序 工具 异常 处理

用于大型程序的工具

--异常处理



引言:

C++语言包含的一些特征在问题比较复杂,非个人所能管理时最为有用。如:异常处理命名空间多重继承

相对于小的程序员团队所能开发的系统需求而言,大规模编程[往往涉及数千万行代码]对程序设计语言的要求更高。大规模应用程序往往具有下列特殊要求:

1.更严格的正常运转时间以及更健壮的错误检测和错误处理。错误处理经常必须跨越独立开发的多个子系统进行[异常处理]。

2.能够用各种库(可能包含独立开发的库)构造程序[命名空间]。

3.能够处理更复杂的应用概念[多重继承&虚继承]。



异常处理

使用异常处理,程序中独立开发的各部分就能够程序执行期间出现的问题相互通信,并处理这些问题。程序的一个部分能够检测出本部分无法解决的问题,这个问题检测部分就可以将问题传递给准备处理问题的其他部分。

【注解】

通过异常我们能够将问题的检测和问题的解决分离,这样程序的问题检测部分可以不必了解如何处理问题

C++的异常处理中,需要由问题检测部分抛出一个对象给处理代码,通过这个对象的类型和内容,两个部分就能够就出现了什么错误进行通信

如:前面曾经介绍过的一个例子:

Sales_item
operator+(const Sales_item &lsh,const Sales_item &rhs)
{
    if (!lsh.same_isbn(rhs))
    {
        throw runtime_error("Data must refer to same ISBN");
    }

    Sales_item ret(lsh);
    ret += rhs;

    return ret;
}

程序中将Sales_item对象相加的部分可以使用一个try块,以便在异常发生时捕获异常:

    Sales_item item1,item2,sum;
    while (cin >> item1 >> item2)
    {
        try
        {
            sum = item1 + item2;
        }
        catch(const runtime_error &e)
        {
            cerr << e.what() << " Try again.\n"
                 << endl;
        }
    }

一、抛出类类型的异常

异常是通过抛出对象而引发的。该对象的类型决定应该激活哪个处理代码。被选中的处理代码是调用链中与该对象类型匹配且离抛出异常位置最近的那个

异常以类似于将实参传递给函数的方式抛出和捕获。异常可以是可传给非引用形参的任意类型的对象,这意味着必须能够复制该类型的对象。

不存在数组或函数类型的异常。相反,如果抛出一个数组,被抛出的对象转换为指向数组首元素的指针,类似的,如果抛出一个函数,函数转换为指向该函数的指针。

执行throw时,不会执行跟在throw后面的语句,而是将控制从throw转移到匹配的catch,该catch可以是同一函数中局部的catch,也可以在直接或间接基类调用发生异常的函数的另一个函数中。控制从一个地方传到另一地方,这有两个重要含义:

1)沿着调用链的函数提早退出

2)一般而言,在处理异常的时候,抛出异常的块中的局部存储不存在了。

因为在处理异常的时候会释放局部存储,所以被抛出的对象就不能在局部存储,而是用throw表达式初始化一个称为异常对象的特殊对象。异常对象由编译器管理,而且保证驻留在可能被激活的任意catch都可以访问的空间。这个对象由throw创建,并被初始化为被抛出的表达式的副本。异常对象将传给对应的catch,并且在完全处理了异常之后撤销。

【小心地雷】

异常对象通过复制被抛出表达式的结果创建,该结果必须是可以复制的类型。



1、异常对象与继承

当抛出一个表达式时,被抛出对象的静态编译时类型将决定异常对象的类型

通常,使用静态类型抛出对象不成问题。当抛出一个异常的时候,通常在抛出点构造将抛出的对象,该对象表示出了什么问题,所以我们知道确切的异常类型



2、异常与指针

如果指针指向继承层次中的一种类型,指针所指对象的类型就有可能与指针的类型不同。无论对象的实际类型是什么,异常对象的类型都与指针的静态类型相匹配。如果该指针是一个指向派生类对象的基类类型指针,则那个对象将被分割,只抛出基类部分。

谨记:抛出指向局部对象的指针总是错误的,因此,在抛出指针的时候,必须确定进入处理代码时指针所指向的对象存在

【小心地雷】

抛出指针通常是个坏主意:抛出指针要求在对应处理代码存在的任意地方存在指针所指向的对象。

//P582 习题17.1
    range_error r("error");
    throw r;	//异常对象类型为 range_error

    exception *p = &r;
    throw *p;	//被异常对象是对指针p进行解引用的结果,其类型与p的静态类型相匹配,为exception


二、栈展开

抛出异常的时候,将暂停当前函数的执行。首先检查throw本身是否在try块内部,如果是,则检查与该try相关的catch子句,看是否其中之一与被抛出对象相匹配。如果找到匹配的catch,就处理异常;如果找不到,就退出当前函数(释放当前函数的内存并撤销局部对象),并且继续在调用函数中查找

如果对抛出异常的函数的调用是在try块中,则检查与该try相关的catch子句。如果找到匹配的catch,就处理异常;如果找不到匹配的catch,调用函数也退出,并且继续在调用这个函数的函数中查找。

这个过程,称之为栈展开,沿嵌套函数调用继续向上,直至为异常找到一个catch子句。只要找到能够处理异常的catch子句,就进入该catch子句,并在该处理代码中继续执行。当catch结束的时候,在紧接在与该try块相关的最后一个catch子句之后的点继续执行。



1、为局部对象调用析构函数

栈展开期间,提早退出包含throw的函数和调用链中可能的其他函数。在释放内存之前,撤销在异常发生之前所创建的所有对象。如果局部对象是类类型的,就自动调用该对象的析构函数。通常,编译器不撤销内置类型的对象

【小心地雷】

栈展开期间,释放局部对象所用的内存并运行类类型局部对象的析构函数



如果一个块直接分配资源,而且在释放资源之前发生异常,在栈展开期间将不会释放该资源。例如,一个块可以通过调用new动态分配内存,如果该块因异常而退出,编译器不会删除该指针,已分配的内在将不会释放。

由类类型对象分配的资源一般会被适当地释放。运行局部对象的析构函数,由类类型对象分配的资源通常由它们的析构函数释放



2、析构函数应该从不抛出异常

在为某个异常进行栈展开的时候,析构函数如果又抛出自己的未经处理的另一个异常,将会导致调用标准库terminate函数。一般而言,terminate函数将调用abort函数,强制从整个程序非正常退出。

因为terminate函数结束程序,所以析构函数做任何可能导致异常的事情通常都是非常糟糕的主意。在实践中,因为析构函数释放资源,所以它不太可能抛出异常。标准库类型都保证它们的析构函数不会引发异常。



3、异常与构造函数

构造函数内部所作的事情经常会抛出异常。在构造函数内部,即使对象只是部分被构造了,也要保证将会适当的撤销已构造的成员。

类似地,在初始化数组或其他容器类型的元素的时候,也可能发生异常,同样,也要保证将会适当地撤销已构造的元素。



4、未捕获的异常终止程序

不能不处理异常。异常是足

首页 上一页 1 2 下一页 尾页 1/2/2
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
分享到: 
上一篇POJ 1150 The Last Non-zero Digi.. 下一篇XTU OJ 1163 查询成绩 (字符串+..

评论

帐  号: 密码: (新用户注册)
验 证 码:
表  情:
内  容: