[C/C++] 第17章 用于大型程序的工具 [ 异常处理,命名空间,多重继承与虚继承 ] (不够熟) 《 C++ Primer 》
第17章:用于大型程序的工具
——异常处理,命名空间,多重继承与虚继承
@学习摘录204:概念
——大规模编程对程序设计语言的要求往往比小程序员团队更高。
——1. 更严格的正常运转时间以及更健壮的错误检测和错误处理。
——2. 能够用各种库(可能包含独立开发的库)构造程序。
——3. 能够处理更复杂的应用概念。
第一节:异常处理
@学习摘录205:异常处理的作用
——通过异常我们能够将问题的检测和问题的解决分离,这样程序的问题检测部分可以不必了解如何处理问题。
@学习摘录206:抛出类型的异常
——异常是通过抛出(throw)对象而引发(raise)的。被选中的处理代码是调用链中与该对象类型匹配且离抛出异常位置最近的那个。
——执行throw的时候,不会执行跟在throw后面的语句,而是将控制从throw转移到匹配的catch.
@学习摘录207:被抛出的对象
——被抛出的对象将会自动转换为一个指针,不存在数组或函数类型的异常。
——如果抛出一个数组,被抛出的对象转换为指向数组首元素的指针。
——如果抛出一个函数,函数被转换为指向该函数的指针。
@学习摘录208:异常对象
——在处理异常的时候,抛出异常的块中的局部存储不存在了。
——因为在处理异常的时候会释放局部存储,所以被抛出的对象就不能再局部存储了,而是用throw表达式初始化一个称为异常对象的特殊对象。
——异常对象将传给对应的catch,并且在完全处理了异常之后撤销。
——当抛出一个表达式的时候,被抛出对象的解态编译时类型将决定异常对象的类型。
@学习摘录209:异常与指针
——用抛出表达式抛出静态类型时,比较麻烦的一种情况是,在抛出中对指针解引用。
——抛出指针的时候,必须确定进入处理代码时指针所指向的对象存在。
——如果抛出指向局部对象的指针,而且处理代码在另一函数中,则执行处理代码时指针所指向的对象将不再存在。
——抛出指针通常是个坏主意:抛出指针要求在对应处理代码存在的任意地方存在指针所指向的对象。
@学习摘录210:栈展开
——沿嵌套函数调用链接继续向上,直至为异常找到一个catch子句,称之为栈展开(stack unwinding)。
——抛出异常时,将暂停当前函数的妨行,开始查找匹配的catch子句。
——首先检查throw本身是否在try块内部
——如果找到匹配的catch,就处理异常
——如果找不到,就退出当前函数,并且继续在调用函数中查找。
——如果一个函数声明没有指定异常说明,则该函数可抛出任意类型的异常。
@学习摘录211:栈释放资源
——栈展开期间,释放局部对象所用的内存并运行类类型局部对象的析构函数。
——如果一个块直接分配资源,而且在释放资源之前发生异常,在栈展开期间将不会释放该资源。
@学习摘录212:析构函数应该从不抛出异常
——在为某个异常进行栈展开的时候,析构函数如果又抛出自己的未经处理的另一个异常,将导致调用标准库terminate函数。
——一般而言,terminate函数将调用abort函数,强制从整个程序非正常退出。
@学习摘录213:异常与构造函数
——与析构函数不同,构造函数内部所做的事情经常会抛出异常。
——类似地,在初始化数组或其他容器类型的元素的时候,也可能发生异常,同样,也要保证将会适当地撤销已构造的元素。
@学习摘录214:捕获异常
——catch子句(catch clause)中的异常说明符(exception specifier)是在其后跟一个(可选)形参名的类型名。
——说明符的类型必须是内置类型。
——在查找匹配的catch期间,找到的catch不必是与异常最匹配的那个catch,相反,将先中第一个找到的可以处理该异常的catch.
@学习摘录215:异常说明符与继承
——如果被抛出的异对象是派生类类型的,但由接受基类类型的catch处理,那么,catch不能使用派生类特有的任何成员。
——通常,如果catch子句处理因继承而相关的类型的异常,它就应该将自己的形参定义为引用。
@学习摘录216:重新抛出
——有可能单个catch不能完全处理一个异常。
——catch可以通过重新抛出将异常传递给函数调用链中更上层的函数。
——如果在处理代码不活动时碰到空throw,就调用terminate函数。
@学习摘录217:捕获所有异常的处理代码
——若不知道可能被抛出的所有异常,可以使用捕获所有异常catch子句(catch-all)的
——如果catch(…)与其他catch子句结合使用,它必须是最后一个,否则,任何跟在它后面的catch子句都将不能被匹配。
@学习摘录218:函数测试块与构造函数
——为了处理来自构造函数初始化式的异常,必须将构造函数编写为函数测试块(function try block).可以使用函数测试块将一组catch子句与函数联成一个整体。
——构造函数要处理来自构造函数初始化式的异常,唯一的方法是将构造函数编写为函数测试块。
@学习摘录219:auto_ptr对象
——auto_ptr和内置指针对待复制和赋值有非常关键的重要区别。
——当复制auto_ptr对象或者将它的值赋给其他auto_ptr对象的时候,将基础对象的所有权从原来的auto_ptr对象转给副本,原来的auto_ptr对象重置为未绑定状态。
@学习摘录220:auto_ptr的缺陷
——1. 不要使用auto_ptr对象保存指向静态分配对象的指针。
——2. 永远不要使用两个auto_ptr对象指向同一对象。
——3. 不要使用auto_ptr对象保存指向动态分配数组的指针。
——4. 不要将auto_ptr对象存储在容器中。
第二节:命名空间
@学习摘录221:命名空间的定义
——命名空间可以在全局作用域或其他作用域内部定义,但不能在函数或类内部定义。
——命名空间名字后面接着由花括号括住的一块声明和定义,可以在命名空间中放入可以出现在全局作用域的任意声明:类、变量、函数、模板以及其它命名空间。
——命名空间作域不能以分号结束。
@学习摘录222:命名是可以是不连续的
——与其他作用域不同,命名空间可以在几个部分中定义。
——命名空间由它的分离定义部分的部和构成,命名空间的累积的。
@学习摘录223:全局命名空间(global namespace)
——定义在全局作用域的名字(在任意类、函数或命名空间外部声明的名字)是定义在全局命名空间中的。
——全局命名空间是隐式声明的,存在于每个程序中,在全局作用域定义实体的每个文件将那些名字加到全局命名空间。
——可以用作用域操作符引用全局命名空间的成员。
——因为全局命名空间是隐含的,它没有名字,所有记号::member_name引用全局命名空间的成员。
@学习摘录224:未命名的命名空间(unnamed namespace)
——未命名的命名空间与其他命名空间不同,未命名的命名空间的定义局部于特定文件,从不跨越多个文本文件。
@学习摘录225:命名空间成员的使用
——除了在函数或其他作用域内部,文件不应该包含using指示或using声明
@学习摘录226:命名空间别名(namespace alias)
——namespace a = b; 此时a 为b 的别名。
@学习摘录227:避免using指示
——using指示注入来自一个命名空间的所有名字,它的使用是靠不住的。
——如果应用程序使用许多库,并且用using指示使得这些库中的名字可见,那么,全局命名空间污染问题就会重新出现。
——相对于依赖于using指示,对程序中使用的每个命名空间名字使用using声明更好,这样做减少注入到命名空间中的名字数目,由using声明引起的二义性错误在声明点而不是使用点检测,因此更容易发现和修正。
@学习摘录228:虚继承
——在虚继承下,对给定虚基类,无论该类在派生层次中作为虚基类出现多少次,只继承一个共享的基类子对象。共享的基类子对象称为虚基类。
——class istream: public virtual ios{…}; class ostream: virtual public ios{…};
——无论虚基类出现在继承层次中任何地方,总是在构造非虚基类之前构造虚基类。
——异常处理,命名空间,多重继承与虚继承
@学习摘录204:概念
——大规模编程对程序设计语言的要求往往比小程序员团队更高。
——1. 更严格的正常运转时间以及更健壮的错误检测和错误处理。
——2. 能够用各种库(可能包含独立开发的库)构造程序。
——3. 能够处理更复杂的应用概念。
第一节:异常处理
@学习摘录205:异常处理的作用
——通过异常我们能够将问题的检测和问题的解决分离,这样程序的问题检测部分可以不必了解如何处理问题。
@学习摘录206:抛出类型的异常
——异常是通过抛出(throw)对象而引发(raise)的。被选中的处理代码是调用链中与该对象类型匹配且离抛出异常位置最近的那个。
——执行throw的时候,不会执行跟在throw后面的语句,而是将控制从throw转移到匹配的catch.
@学习摘录207:被抛出的对象
——被抛出的对象将会自动转换为一个指针,不存在数组或函数类型的异常。
——如果抛出一个数组,被抛出的对象转换为指向数组首元素的指针。
——如果抛出一个函数,函数被转换为指向该函数的指针。
@学习摘录208:异常对象
——在处理异常的时候,抛出异常的块中的局部存储不存在了。
——因为在处理异常的时候会释放局部存储,所以被抛出的对象就不能再局部存储了,而是用throw表达式初始化一个称为异常对象的特殊对象。
——异常对象将传给对应的catch,并且在完全处理了异常之后撤销。
——当抛出一个表达式的时候,被抛出对象的解态编译时类型将决定异常对象的类型。
@学习摘录209:异常与指针
——用抛出表达式抛出静态类型时,比较麻烦的一种情况是,在抛出中对指针解引用。
——抛出指针的时候,必须确定进入处理代码时指针所指向的对象存在。
——如果抛出指向局部对象的指针,而且处理代码在另一函数中,则执行处理代码时指针所指向的对象将不再存在。
——抛出指针通常是个坏主意:抛出指针要求在对应处理代码存在的任意地方存在指针所指向的对象。
@学习摘录210:栈展开
——沿嵌套函数调用链接继续向上,直至为异常找到一个catch子句,称之为栈展开(stack unwinding)。
——抛出异常时,将暂停当前函数的妨行,开始查找匹配的catch子句。
——首先检查throw本身是否在try块内部
——如果找到匹配的catch,就处理异常
——如果找不到,就退出当前函数,并且继续在调用函数中查找。
——如果一个函数声明没有指定异常说明,则该函数可抛出任意类型的异常。
@学习摘录211:栈释放资源
——栈展开期间,释放局部对象所用的内存并运行类类型局部对象的析构函数。
——如果一个块直接分配资源,而且在释放资源之前发生异常,在栈展开期间将不会释放该资源。
@学习摘录212:析构函数应该从不抛出异常
——在为某个异常进行栈展开的时候,析构函数如果又抛出自己的未经处理的另一个异常,将导致调用标准库terminate函数。
——一般而言,terminate函数将调用abort函数,强制从整个程序非正常退出。
@学习摘录213:异常与构造函数
——与析构函数不同,构造函数内部所做的事情经常会抛出异常。
——类似地,在初始化数组或其他容器类型的元素的时候,也可能发生异常,同样,也要保证将会适当地撤销已构造的元素。
@学习摘录214:捕获异常
——catch子句(catch clause)中的异常说明符(exception specifier)是在其后跟一个(可选)形参名的类型名。
——说明符的类型必须是内置类型。
——在查找匹配的catch期间,找到的catch不必是与异常最匹配的那个catch,相反,将先中第一个找到的可以处理该异常的catch.
@学习摘录215:异常说明符与继承
——如果被抛出的异对象是派生类类型的,但由接受基类类型的catch处理,那么,catch不能使用派生类特有的任何成员。
——通常,如果catch子句处理因继承而相关的类型的异常,它就应该将自己的形参定义为引用。
@学习摘录216:重新抛出
——有可能单个catch不能完全处理一个异常。
——catch可以通过重新抛出将异常传递给函数调用链中更上层的函数。
——如果在处理代码不活动时碰到空throw,就调用terminate函数。
@学习摘录217:捕获所有异常的处理代码
——若不知道可能被抛出的所有异常,可以使用捕获所有异常catch子句(catch-all)的
——如果catch(…)与其他catch子句结合使用,它必须是最后一个,否则,任何跟在它后面的catch子句都将不能被匹配。
@学习摘录218:函数测试块与构造函数
——为了处理来自构造函数初始化式的异常,必须将构造函数编写为函数测试块(function try block).可以使用函数测试块将一组catch子句与函数联成一个整体。
——构造函数要处理来自构造函数初始化式的异常,唯一的方法是将构造函数编写为函数测试块。
@学习摘录219:auto_ptr对象
——auto_ptr和内置指针对待复制和赋值有非常关键的重要区别。
——当复制auto_ptr对象或者将它的值赋给其他auto_ptr对象的时候,将基础对象的所有权从原来的auto_ptr对象转给副本,原来的auto_ptr对象重置为未绑定状态。
@学习摘录220:auto_ptr的缺陷
——1. 不要使用auto_ptr对象保存指向静态分配对象的指针。
——2. 永远不要使用两个auto_ptr对象指向同一对象。
——3. 不要使用auto_ptr对象保存指向动态分配数组的指针。
——4. 不要将auto_ptr对象存储在容器中。
第二节:命名空间
@学习摘录221:命名空间的定义
——命名空间可以在全局作用域或其他作用域内部定义,但不能在函数或类内部定义。
——命名空间名字后面接着由花括号括住的一块声明和定义,可以在命名空间中放入可以出现在全局作用域的任意声明:类、变量、函数、模板以及其它命名空间。
——命名空间作域不能以分号结束。
@学习摘录222:命名是可以是不连续的
——与其他作用域不同,命名空间可以在几个部分中定义。
——命名空间由它的分离定义部分的部和构成,命名空间的累积的。
@学习摘录223:全局命名空间(global namespace)
——定义在全局作用域的名字(在任意类、函数或命名空间外部声明的名字)是定义在全局命名空间中的。
——全局命名空间是隐式声明的,存在于每个程序中,在全局作用域定义实体的每个文件将那些名字加到全局命名空间。
——可以用作用域操作符引用全局命名空间的成员。
——因为全局命名空间是隐含的,它没有名字,所有记号::member_name引用全局命名空间的成员。
@学习摘录224:未命名的命名空间(unnamed namespace)
——未命名的命名空间与其他命名空间不同,未命名的命名空间的定义局部于特定文件,从不跨越多个文本文件。
@学习摘录225:命名空间成员的使用
——除了在函数或其他作用域内部,文件不应该包含using指示或using声明
@学习摘录226:命名空间别名(namespace alias)
——namespace a = b; 此时a 为b 的别名。
@学习摘录227:避免using指示
——using指示注入来自一个命名空间的所有名字,它的使用是靠不住的。
——如果应用程序使用许多库,并且用using指示使得这些库中的名字可见,那么,全局命名空间污染问题就会重新出现。
——相对于依赖于using指示,对程序中使用的每个命名空间名字使用using声明更好,这样做减少注入到命名空间中的名字数目,由using声明引起的二义性错误在声明点而不是使用点检测,因此更容易发现和修正。
@学习摘录228:虚继承
——在虚继承下,对给定虚基类,无论该类在派生层次中作为虚基类出现多少次,只继承一个共享的基类子对象。共享的基类子对象称为虚基类。
——class istream: public virtual ios{…}; class ostream: virtual public ios{…};
——无论虚基类出现在继承层次中任何地方,总是在构造非虚基类之前构造虚基类。