C 11学习笔记 17

发布 2022-06-17 16:03:28 阅读 1597

大规模应用程序的特殊要求包括:

在独立开发的子系统之间协同处理错误的能力。

使用各种库(可能包含独立开发的库)进行协同开发的能力。

对比较复杂的应用的概念建模的能力。

对应的就是:异常处理、名字空间、多重继承。

异常处理机制允许程序中独开发的部分在运行时就出现的问题进行通信并作出相应的处理。异常使得我们能够将问题的检测和解决过程分离开来。程序的一部分负责检测问题的出现,然后解决该问题的任务传递给程序的另一部分。

检测环节无须问题处理模块的所有细节,反之亦然。

在c++中我们通过抛出一条表达式来引发一个异常。被抛出的表达式的类型以及当前的调用链共同决定了哪段处理**将被用来处理该异常。

控制权从一处转移到另一处,这有两个重要含义:

沿着调用链的函数可能会提早退出。

一旦程序开始执行异常处理**,则沿着调用链创建的对象将被销毁。

栈展开过程就是沿着嵌套函数的调用链不断查找,直到找到了与异常匹配的catch子语句为止;或者也可能一直没有找到匹配的catch,则推出主函数后查找过程终止。

假设找到了一个匹配的catch子语句,则程序进入该子句并执行其中的**。当执行完这个catch子句之后,找到与try块关联的最后一个catch子句之后的点,并从这里继续执行。

如果没有找到匹配的catch子句,程序将退出。因为异常通常被认为是妨碍程序正常执行的事件,所以一旦引发了某个异常,就不能对它置之不理。当找不到匹配的catch时,程序将调用标准函数terminate,负责终止程序的执行过程。

如果异常发生在构造函数中,则当前的对象可能只构造了一部分。有的成员已经初始化了,而另一些程序在异常发生前也许还没有被初始化。即使某个对象只构造了一部分,我们也要确保已构造的成员能被正确地销毁。

在数组或标准容器的初始化中,也是这样的。

在一个函数中负责释放字符的**可能被跳过,如果在释放资源的**前发生了异常。但是,类对象的资源是由类的析构函数负责释放。因此,如果我们使用类来控制资源分配,就能确保无论函数正常结束还是遭到异常,资源都可以被正确的释放。

析构函数在栈展开的过程中执行,这一事实影响着我们编写析构函数的方式。在栈展开的过程中,已经引发了异常但是我们还没有处理它。如果异常抛出后没有被正确的捕获,则系统将调用terminate函数。

因此,出于对栈展开可能使用析构的考虑,析构函数不应该抛出不能被它自身处理的异常。换句话,如果析构函数需要执行某个可能抛出异常的操作,则该操作应该被放在一个try语句块中,并且在析构函数内部得到处理。

在实际编程中,因为析构函数仅仅是释放资源,所以它不太可能抛出异常。所有标准类型都能确保它们的析构函数不会抛出异常。

异常对象是一种特殊的对象,编译器使用异常表达式对异常对象进行拷贝初始化。因此,throw语句中的表达式必须拥有完全类型。而且如果该表达式是类类型的话,则相应的类必须含有一个可访问的析构函数和一个可访问的拷贝或移动构造函数。

如果该表达式是数组类型或函数类型,则表达式将被转换成与之对应的指针类型。

异常对象位于由编译器管理的空间中,编译器确保无论最终调用哪个catch子句都能访问该空间。当异常处理完毕后,异常对象被销毁。

当一个异常被抛出时,沿着调用函数链的块将依次推出直至找到与异常匹配的处理**。如果退出了某个快则同时释放快中局部对象使用的内存。因此,抛出一个指向局部对象的指针几乎肯定是错误了。

同样的,从一个函数中返回指向局部对象的真正也是错误的。如果所有指针所指的对象位于某个块中,而该快在catch语句之前就已经推出了,则意味着在执行catch语句之前局部对象已经被销毁了。

当我们抛出一条表达式时,该表达式的静态编译时类型决定了异常对象的类型。因为在很多情况下程序的表达式类型来自于某个继承体系。如果一条throw表达式解引用一个基类指针,而该指针实际指向的是派生类对象,则抛出的对象将被切掉一部分,只有基类部分别抛出。

抛出指针要求在任何对象的处理**存在的地方,指针所指的对象都必须存在。

catch语句中,如果catch无须访问抛出的表达式的话,则我们可以忽略捕获形参的名字。

声明的类型决定了处理**所能捕获的异常类型。这个类型必须是完全类型,它可以是左值引用,但不能是右值引用。

如果catch的参数是基类类型,则我们可以使用其派生类类型的异常对象对其进行初始化。此时,如果catch的参数是非引用类型,则异常对象将被切掉一部分,这与将派生类对象以值传递的方式给一个普通函数差不多。另一方面,如果catch的参数是基类的引用,则该参数将异常规方式绑定到异常对象上。

异常声明的静态类型决定catch语句所能执行的操作。如果catch的参数是基类类型,则catch无法使用派生类特有的任何成员。

通常情况下,如果catch接受异常与某个继承体系有关,则最好将该catch的参数定义成引用类型。

在搜寻catch语句的过程中,我们最终找到的catch未必是异常的最佳匹配。相反,挑选出来的应该是第一个与异常匹配的catch语句。因此,越是专门的catch越应该置于整个catch列表的前端。

catch语句是按照其出现的顺序逐一进行匹配的,所以当程序使用具有继承关系的多个异常时必须对catch语句的顺序进行组织和管理,使得派生类异常的处理**出现在基类异常的处理**之前。

异常和catch异常声明的匹配规则受到更多限制。此时,绝大多数类型转换都不被允许,除了一些极细小的差别之外,要求异常的类型和catch声明的类型是精确匹配的:

允许从非常量向常量的类型装换,也就是说,一条非常量对象的throw语句可以匹配一个接受常量引用的catch语句。

允许从派生类向基类的类型转换。

数组被转换成指向数组(元素)类型的指针,函数被转换成指向函数类型的指针。

除此之外,包括标准算术类型转换和类类型转换在内,其他所有转换规则都不能再匹配catch的过程中使用。

如果在多个catch语句的类型之间存在着继承关系,则我们应该把继承链最低端的类放在前面,而将继承链最顶端的类放在后面。

重新抛出:空的catch语句只能出现在catch语句或catch语句直接或间接调用的函数之内。如果在处理**之外的区域遇到了空throw语句,编译器将调用terminate。

一个重新抛出语句并不指定新i型的表达式,而是将当前的对象沿着调用链向上传递。

很多时候,catch语句会改变参数的内容。如果在改变了参数的内容后catch语句重新抛出异常,则只有当catch异常声明是引用类型时我们对参数所做的改变才会被保留并继续传播。

catch (my_error &eobj引用类型。

= errcodes::severeerr; /修改了异常对象。

throw异常对象的status成员是severeerr

catch (other_error eobj) /非引用类型。

= errcode::baderr; /只修改了异常对象的局部副本。

throw异常对象的status成员没有改变。

为了一次性捕获所有异常,我们使用省略号作为异常声明,这样的处理**称为捕获所有异常的处理**。可以与任意类型的异常匹配。

void manip( )

trycatch(..

catch(..可以单独出现,也可以与其他语句一块出现。但它应该在最后的位置。他对任何类型的异常都匹配。

特别的异常容易发生在处理构造函数初始值的过程中。构造函数在进入其函数体之前首先执行初始值列表。因为在初始值列表抛出异常时构造函数体内的try语句块还未生效,所以构造函数体内的catch语句无法处理构造函数初始值列表抛出的异常。

要想处理构造函数初始值抛出的异常,我们必须将构造函数写成函数try语句块(函数测试块)的形式。

template

blob::blob(std::initializer_list il)try :

data(std::make_shared>(il)

catch (const std::bad_alloc &e)

注意try的位置。

还有一种情况值得注意,在初始化构造函数的参数时也可能发生异常,这样的异常不属于函数try语句块的一部分。函数try只能处理构造函数开始执行后发生的异常。和其他函数调用一样,如果在参数初始化的过程中发生了异常,则该异常属于调用表达式的一部分,并将在调用者所在的上下文中处理。

在c++11中,我们可以通过提供noexcept说明指定某个函数不会抛出异常。

void recoup(int) noexcept不会抛出异常,做了不抛出说明。

void alloc(int可能抛出异常。

noexcept说明要么出现在该函数的所有声明和定义语句中,要么一次也不出现。该说明应该在函数的尾返回之前。我们可以在函数指针的声明和定义中指定noexcept。

在typedef或类型别名中不能出现noexcept。在成员函数中,noexcept说明符需要跟在const及引用限定符之后,而在final、override或虚函数的=0之前。

编译器并不会在编译时检查noexcept说明。实际上,如果一个函数在说明了noexcept的同时又含有throw语句或者调用了可能抛出异常的其他函数,编译器将顺利通过,并不因为这种违反异常说明的情况而报错。

因此可难呢过出现这样的情况:尽管函数声明了它不会抛出异常,但实际上还是抛出了。一旦一个noexcept函数抛出了异常,程序就会调用terminate以确保遵守不在运行时抛出异常的承诺。

上述郭晨对释放执行栈展开未做约束,因此noexcept可以用在两种情况下:一是我们确认函数不会抛出异常,而是我们根本不知道应该如何处理异常。

C11学习笔记 5

参数传递时,如果是引用类型,它将绑定到对应的实参上,否者,将实参的值拷贝后赋给形参。引用可避免拷贝,如果不需要改变参数,最好定义成常量引用。和其他初始化一样,当用实参初始化时会忽略顶层const,当形参有顶层const时,传给它常量对象或非常量对象都可以。我们可以用非常量初始化一个底层const对象...

C 11学习笔记 11

动态分配的内存,只有在显式释放是,这些对象才会销毁。但是标准库中的两智能指针可以确保自动释放。除了静态内存和栈内存,每个程序还拥有一个内存池。这部分内存被称作自由空间或堆。静态内存用来保存局部static对象 类static数据成员以及定义在任何函数之外的变量。栈内存用来保存定义在函数没得非stat...

C 11学习笔记 16

tuple是类型pair的模板。不同tuple类型的成员类型也不同,但一个tuple可以有任意数量的成员。每个确定的tuple类型的成员数目是固定的。当我们希望将一些数据组合成单一对象,但有不想麻烦地定义一个新数据结构来表示时,tuple是非常有用的。快速而随意 的数据结构 tuple类型及其伴随类...