C++ 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、未捕获的异常终止程序
不能不处理异常。异常是足够重要的、使程序不能继续正常运行的事件。假设找不到匹配的catch,程序就调用库函数terminate[你懂得。。。]!
三、捕获异常
catch子句中的异常说明符看起来像仅仅包括一个形參的形參表,异常说明符是在其后跟一个(可选)形參名的类型名。
说明符的类型决定了处理代码能够捕捉的异常种类。类型必须是全然类型,即必须是内置类型或者是已经定义了的程序猿自己定义的类型。类型的前向声明不行。
当catch为了处理异常仅仅须要了解异常的类型的时候,异常说明符能够省略形參名;假设处理代码须要已发生异常的类型之外的信息,则异常说明符就包括形參名,catch使用这个名字訪问异常对象。
1、查找匹配的处理代码
在查找匹配的catch期间,找到的catch不必是与异常最匹配的那个,相反,将选中第一个找到的能够处理该异常的catch。因此,在catch子句列表中,最特殊的catch必须最先出现。
异常与catch异常说明符匹配:大多数转换都不同意 --除以下几种可能的差别之外,异常的类型与catch说 明符的类型必须全然匹配:
1)同意从非const到const的转换。也就是说,非const对象的 throw能够与指定接受const引用的 catch匹配。
2)同意从派生类型型到基类类型的转换。
3)将数组转换为指向数组类型的指针,将函数转换为指向函数类型的适当指针。
在查找匹配catch的时候,不同意其它转换。详细而言:既不同意标准算术转换,也不同意为类类型定义的转换[好绝情%>_<%]。
2、异常说明符
进入catch的时候,用异常对象初始化catch的形參。像函数形參一样,异常说明符类型能够是引用。异常对象本身是被抛出对象的副本。是否再次将异常对象拷贝到catch位置取决于异常说明符类型。
假设说明符不是引用,就将异常对象拷贝到catch形參中,对形參所做的不论什么改变都仅仅作用于副本,不会作用于异常对象本身。假设说明符是引用,则像引用形參一样,不存在单独的catch对象,
catch形參仅仅是异常对象的还有一名字。对catch形參所做的改变作用于异常对象。
3、异常说明符与继承
像形參声明一样,基类的异常说明符能够用于捕获派生类型的异常对象,并且,异常说明符的静态类型决定catch子句能够运行的动作。假设被抛出的异常对象是派生类类型的,但由接受基类类型的catch处理,那么,catch不能使用派生类特有的不论什么成员。
【最佳实践】
通常,假设catch子句处理因继承而相关的类型的异常,它就应该将自己的形參定义为引用。此时catch对象的静态类型能够与catch对象所引用的异常对象的动态类型不同。
假设catch对象是基类类型对象而异常对象是派生类型的,就将异常对象切割为它的基类子对象。
对象(相对于引用)不是多态的。对象的静态类型和动态类型相同,函数是虚函数也一样。仅仅有通过引用或指针调用时才发生动态绑定,通过对象调用不进行动态绑定。
4、catch子句的次序必须反映类型层次
将异常类型组织成类层次的时候,用户能够选择应用程序处理异常的粒度级别。比如,仅仅希望清除并退出的应用程序能够定义一个try块,该try块包围main函数中带有例如以下catch代码:
catch(exception &e)
{
cerr << "Exiting: " << e.what() << endl;
size_t status_indicator = 42;
return(status_indicator);
}
有更严格实时需求的程序可能须要更好的异常控制,这种应用程序将清除导致异常的一切并继续运行。
由于catch子句按出现次序匹配,所以使用来自继承层次的异常的程序将它们的catch子句排序,以便派生类型的处理代码出如今其基类类型的catch之前。
【注解】
带有因继承而相关的类型的多个catch子句,必须从最低层派生类型到最高派生类型排序。例如以下题:
//P585 习题17.3
try
{
//...
}
catch(overflow_error eobj)
{
//...
}
catch(const runtime_error &re)
{
//...
}
catch(exception)
{ }
C++ Primer 学习笔记_87_用于大型程序的工具 --异常处理的更多相关文章
- C++ Primer 学习笔记_88_用于大型程序的工具 --异常处理[续1]
用于大型程序的工具 --异常处理[续1] 四.又一次抛出 有可能单个catch不能全然处理一个异常.在进行了一些校正行动之后,catch可能确定该异常必须由函数调用链中更上层的函数来处理,catch能 ...
- C++ Primer 学习笔记_95_用于大型程序的工具 --多重继承与虚继承
用于大型程序的工具 --多重继承与虚继承 引言: 大多数应用程序使用单个基类的公用继承,可是,在某些情况下,单继承是不够用的,由于可能无法为问题域建模,或者会对模型带来不必要的复杂性. 在这些情况下, ...
- C++ Primer 学习笔记_91_用于大型程序的工具 --命名空间
用于大型程序的工具 --命名空间 引言: 在一个给定作用域中定义的每一个名字在该作用域中必须是唯一的,对庞大.复杂的应用程序而言,这个要求可能难以满足.这样的应用程序的全局作用域中一般有很多名字定义. ...
- 【c++ Prime 学习笔记】第18章 用于大型程序的工具
大规模应用程序的特殊要求包括: 在独立开发的子系统之间协同处理错误:异常处理 使用各种库(可能包含独立开发的库)进行协同开发:命名空间 对比较复杂的应用概念建模:多重继承 18.1 异常处理 异常处理 ...
- 【C++ Primer】用于大型程序的工具
1. 异常处理 异常以类似于将实參传递给函数的方式抛出和捕获.异常可以是可传给非引用实參的随意实參的类型,这意味着必须可以复制该类型的对象. 当抛出一个表达式的时候,被抛出对象的静态编译时类型将决定异 ...
- C++ Primer 5th 第18章 用于大型程序的工具
C++大规模程序设计至少存在三个特殊要求: 错误处理 库的引入 复杂建模 以上三种对应C++语言的三种特性:异常处理.命名空间.多重继承. 异常处理 异常处理机制是一种允许偷懒的工具,在出现非正确的情 ...
- C++ 用于大型程序的工具
<C++ Primer 4th>读书笔记 相对于小的程序员团队所能开发的系统需求而言,大规模编程对程序设计语言的要求更高.大规模应用程序往往具有下列特殊要求: 1. 更严格的正常运转时间以 ...
- C/C++基础----用于大型程序的工具(异常处理,命名空间,多重继承)
独立开发的子系统间协同处理错误的能力 使用各种库(可能包含独立开发的库进行协同开发的能力) 对比复杂的应用概念建模的能力 异常处理 异常将问题的检测和解决过程分离开 当执行一个throw之后,程序控制 ...
- C++ Primer学习笔记(二)
题外话:一工作起来就没有大段的时间学习了,如何充分利用碎片时间是个好问题. 接 C++ Primer学习笔记(一) 27.与 vector 类型相比,数组的显著缺陷在于:数组的长度是固定的,无法 ...
随机推荐
- 【JAVA】导出jar包时,Class files on classpath not found
是因为\META-INF\MANIFEST.MF文件里面配置错误 错误版本 Manifest-Version: 1.0Class-Path: 正确版本 Manifest-Version: 1.0Cla ...
- 关于tableView刷新
UITabelView的局部刷新 1. 刷新整个tableView用[self.tableView reloadData]; 2. [self.tableView reloadRowsAtIndexP ...
- Java学习之finally
如果catch中有return语句,finally里面的语句还会执行吗? 会执行,在return语句的中间执行 public class Test{ public static void main(S ...
- 关于LZO和LZOP
LZO 是一个适合实时解压.压缩的压缩库 LZOP 基于LZO库的压缩解压工具 PS:有了压缩解压库LZO,还不能直接操作文件压缩解压,需要LZOP 下载的话直接google吧~~~
- 微信 php 获取ticket
<?phpheader('content-type:text/html; charset=utf8');define('TOKEN', 'youtoken'); // TOKENdefine(' ...
- Python BeautifulSoup中文乱码问题的2种解决方法
解决方法一: 使用python的BeautifulSoup来抓取网页然后输出网页标题,但是输出的总是乱码,找了好久找到解决办法,下面分享给大家首先是代码 from bs4 import Beautif ...
- IOS 播放音频
1,播放短音频 #import <AudioToolbox/AudioToolbox.h>#import "GLYViewController.h"static voi ...
- ASP.NET MVC进阶之路:依赖注入(Di)和Ninject
0X1 什么是依赖注入 依赖注入(Dependency Injection),是这样一个过程:某客户类只依赖于服务类的一个接口,而不依赖于具体服务类,所以客户类只定义一个注入点.在程序运行过程中,客户 ...
- 如何管理安卓android手机下google(谷歌)的通讯录联系人账户
andorid手机都自带通讯录备份功能,但是如何管理,一直是一些人头疼的问题.经常在手机备份还原之后发现很多联系人都有重复. 1.打开 :https://mail.google.com/ 用你的谷歌账 ...
- Android Studio 新建项目的R文件丢失的解决方法
最近Android Studio炒的比较热,于是笔者决定赶赶时髦,从Eclipse转到了Android Studio.不幸的是,用Android Studio创建项目的时候就遇到了一个比较尖锐的问题— ...