第18章 用于大型程序的工具

1.  独立开发的库几乎不可避免的使用彼此相同的名字,一个库中定义的名字可能会与其他库中的相同名字冲突.为了避免冲突,可以将名字定义在namespace.

2.  通过异常.我们能够将问题的检测和问题的解决分离,这样程序的问题检测部分可以不必了解如何处理问题.
  
有效使用异常处理需要理解
:
      
■ 在抛出异常时会发生什么

      
■ 在捕获异常时又会发生什么
      
■ 用来传递错误的对象的含义
  
异常是通过抛出对象而引发的,该对象的类型决定应该激活哪个处理代码.被选中的处理代码是调用链中与该对象类型匹配却离抛出异常位置最近的那个.
  
执行throw的时候,不会执行跟在throw后面的语句,而是将控制从throw转移到匹配的catch,catch可是是同一函数中局部的catch,也可以在直接或间接调用发生异常的函数的另一个函数中.控制从一个地方转移到另一个地方的重要含义有
:
     1). 
沿着调用链的函数提早退出
.
     2). 
一般而言,在处理异常的时候会释放局部存储,所以被抛出的对象就不能在局部存储,而是用throw表达式初始化一个称为异常对象的特殊对象
.
  
异常对象通过复制被抛出表达式的结果创建,该结果必须是可以复制的类型
.
  
当抛出一个表达式的时候,被抛出对象的静态编译时类型将决定异常对象的类型
.
  
抛出指针通常是一个坏主意:抛出指针要求在对应处理代码存在的任意地方存在指针指向的对象.如果抛出的是指针的解引用.则对象不发生动态绑定.而是截断
.
  
栈展开期间,释放局部对象所用的内存并运行类类型局部对象的析构函数
.
  
在某些异常进行栈展开的时候,析构函数如果又抛出自己的未经处理的另一个异常,将会导致调用标准库terminate函数,一般而言,terminate函数将调用abort函数,强制从整个程序非正常退出.(所以我们一般也不考虑析构函数抛出异常).

3.  查找匹配的处理代码时,异常类型与catch说明符的类型必须完全匹配:
   
■ 允许从非constconst的转换,也就是说,const对象的throw可以与指定接受const引用的catch匹配
.
   
■ 允许从派生类型到基类类型的转换

   
■ 将数组转化为指向数组类型的指针,将函数转换为指向函数类型的适当的指针.
在查找catch,不允许标准算术转化,也不允许为类类型定义的转换
.
  
通常,如果catch字句处理因继承而相关的类型的异常,它就应该将自己的形参定义为引用.带有因继承而相关的类型的多个catch字句,必须从最低派生类型到最高派生类型排序.

4.  catch可以通过重新抛出将异常传递给函数调用链中更上层的函数.重新抛出是后面不跟类型或者表达式的一个throw: .throw语句将重新抛出异常对象,它只能出现在catch或者从catch调用的函数中,如果在处理代码不活动是碰到空throw,就调用terminate函数.
  
虽然重新抛出不指定自己的异常,但仍然见一个异常对象沿链向上传递,被抛出的异常是原来的异常对象,而不是catch形参
.
  
捕获所有异常catch字句
:
    

  
为了处理来自构造函数初始化式的异常,必须将构造函数编写为函数测试块.可以使用函数测试块将一组catch字句与函数连成一个整体.

注意,关键字try出现在成员函数初始化之前并且测试的复合语句包围了构造函数的函数体.catch字句既可以处理从成员初始化列表中抛出的异常,也可以处理从构造函数函数体中抛出的异常.(构造函数要处理来自构造函数初始化的异常,唯一的方法时间构造函数编写为函数测试块.

5.  标准库异常类的继承:

exception
类型所定义的唯一操作是一个名为What的虚成员,该函数返回const char*对象,它一般返回用来在抛出位置构造异常对象的信息.

6.  通过定义一个类来封装资源的分配和释放,可以保证正确的释放资源,这一技术常称为资源分配即初始化简称RAII
  
可能存在异常的程序以及分配资源的程序应该使用类来管理那些资源.使用类管理分配和回收可以保证如果发生异常就释放资源.

7.  auto_ptr是接收一个类型形参的模板,它为动态分配的对象提供异常安全
  auto_ptr
只能用于管理从new返回的一个对象,它不能管理动态分配的数组.正如我们所见,auto_ptr被复制或者赋值时,有不寻常的行为,因此不能将auto_ptr存储在标准库容器类型中.
  auto_ptr对象只能保存一个指向对象的指针,并且不能用于指向动态分配的数组,使用auto_ptr对象指向动态分配的数组会导致未定义的运行时行为.
  
■ 为异常安全的内存分配使用auto_ptr(它能在任何时候释放内存
)
  
■ auto_ptr是可以保存任何类型指针的模板

  
■ 将auto_ptr绑定到指针:(接收指针的构造函数为explicit,即必须使用初始化的直接形式来创建auto_ptr对象
  
■ auto_ptr对象的复制和赋值具有破坏性操作
  auto_ptr
和内置指针对待赋值和复制有非常关键的重要区别.当复制auto_ptr对象或者将它的值赋值给其他的对象时,将基础对象的所有权从原来的auto_ptr对象转给副本,原来的auto_ptr对象重置为未绑定状态.
  
另外,与其他赋值后者复制不同,auto_ptr的赋值和复制改变右操作数,因此赋值的左右操作数必须是可以修改的左值
.
  
注意:因为复制和赋值是破坏性操作,所以不能将auto_ptr对象存储在标准容器中.标准库的容器类型要求在复制或赋值之后两个对象相等,auto_ptr不满足这一要求,如果将ap2赋给ap1,则赋值之后ap1!=ap2,复制也类似
.
  
■ 如果要检查指针是否未绑定,可以在条件中直接测试指针,效果是确定指针是否为0,相反,不能直接测试auto_ptr对象
.
     auto_ptr
类型没有定义可用作条件的类型的转换,相反,要测试auto_ptr对象,必须使用它的get成员,该成员返回包含在auto_ptr对象中的基础指针(可将get后的值和0比较
).
   
注意:应该只用get询问auto_ptr对象或者使用返回的指针值,不能用get作为创建其他auto_ptr对象的实参
.   
  
 auto_ptr对象与内置指针的另一个区别是:不能直接将一个地址(或者其他指针)赋给auto_ptr对象
.
          p_auto = new int(1024);
相反必须使用reset函数来改变指针
:
       if(p_auto.get())
       {
           *p_auto = 1024;
       }
       else
       {
           p_auto.reset(new int(1024));
       }
注意:调用auto_ptr对象的reset函数时,在将auto_ptr对象绑定到其他对象之前,会删除auto_ptr对象所指的对象(如果存在),但是,正如自身赋值是没有效果的一样,如果调用该auto_ptr对象已经保存的同一指针的reset函数,也没有效果,不会删除对象.

8.  异常说明指定,如果函数抛出异常,被抛出异常将是包含在该说明中的一种,或者是从列出的异常中派生的类型.
   
使用auto_ptr的限制
:
   
● 不要使用auto_ptr对象保存指向静态分配对象的指针,否则,auto_ptr对象本身撤销的时候,它将试图删除指向非动态分配对象的指针,导致未定义的行为
.
   
● 永远不要是有那个两个auto_ptr对象指向同一个对象,导致这个错误的一种明显方式是,使用同一指针来初始化或者reset两个不同的auto_ptr对象,另一种导致这个错误的微妙的方式可能是,使用一个auto_ptr对象的get函数的接过来初始化或者reset另一个auto_ptr对象
.
   
● 不要使用auto_ptr对象保存指向动态分配数组的指针.auto_ptr对象被删除时,它释放一个对象它使用普通delete操作符,而非delete[]操作符

   
● 不要将auto_ptr对象存储在容器中.容器要求所保存的类型定义复制和赋值操作符,是它们表现得类似于内存类型的操作符:在复制(或者赋值)之后,两个对象必须具有相同值,auto_ptr不具备这个特性.

9.  如果一个函数声明没有指定异常说明,则该函数可以抛出任意类型的异常.
  
如果函数抛出了没有在其异常说明中列出的异常,就调用标准库函数unexpected.默认情况下,unexpected函数调用terminal函数,该函数会终止程序

  
在编译的时候,编译器不能也不会试图验证异常说明.
  
基类中虚函数异常说明,可以与派生类中对应虚函数的异常说明不同.但是派生类虚函数的异常说明必须与对应基类虚函数的异常说明同样严格,或者比基类更严格
.
  
在另一指针初始化带异常说明的函数的指针,或者将后者赋值给函数地址的时候,两个指针的异常说明不必相同.但是.源指针的异常说明必须至少与目标指针一样严格.

10. 命名空间可以在全局作用域或者其他作用域的内部定义,但是不能在函数或类内部定义.需要注意的是:命名空间不能以分号结束.
  
在命名空间中定义的名字可以被命名空间中的其他成员直接访问,命名空间外部的代码必须指出名字定义在哪个命名空间中
.
  
命名空间由它的分离定义部分的总和构成,命名空间是累积的
.
  
可以用于管理自己的类和函数定义相同的方法来组织命名空间
:
    
● 定义类的命名空间成员,以及作为类接口一部分的函数声明与对象声明,可以放在头文件中,使用命名空间成员的文件可以包含这些头文件
.
    
● 命名空间成员的定义可以放在单独的源文件中
.
  
定义多个不相关的类型的命名空间应该使用分离的文件,表示该命名空间定义的每个类型
.
  
不能在不相关的命名空间中定义成员
.
  
全局命名空间使用来引用其成员(类似于VC中的Win32 API)

11. 未命名的命名空间与其他命名空间不同,未命名的命名空间的定义局部于特定文件,从不跨越多个文件
  
如果在文件的最外层作用域中定义未命名的命名空间,那么,未命名的命名空间中的名字必须与全局作用域中的定义的名字不同.
  
如果头文件定义了未命名的命名空间,那么在每个包含该头文件的文件中,该命名空间中的名字将定义不同的局部实体
.
  C++
不赞成文件静态声明.不赞成的特征是在未来版本中可能不支持的特征.应该避免文件静态而使用未命名的命名空间代替.

12. 命名空间头文件的使用:除了在函数或者其他作用域内部,头文件不应该包含Using指示或Using声明.在顶级作用域包含Using指示或Using声明的头文件,具有将改名字注入包含该头文件的文件中的效果.头文件应该只定义作为其接口的一部分的名字,不要定义在其实现中使用的名字.
  
命名空间别名
:namespace primer = cplusplus_primer;
  
用非常相似的方式确定类成员定义中使用的名字,只有一个重要的区别:如果名字不是局部于成员函数,就试着在查找更外层作用域之前在类成员中确定名字
.
  
可以从函数的限定名推断出查找名字是所检查作用域的次序,限定名以相反次序指出被查找的作用域
.
接接受类类型形参(或类类型指针及引用)的函数(包括重载操作符),以及鱼类本身定义在同一命名空间中的函数(包括重载操作符),在用类类型对象(或类类型的引用或者指针)作为实参的时候是可见的.说白了意思就是,在函数的参数中如果有该函数所属类,那么该函数可以不加限定名(类名
).
  
如果类在命名空间内部定义,则没有另外声明的友元函数在同一命名空间中声明(个人注解:在本书的翻译中,有一些地方是很难理解的,或者可以说翻译的很牵强,为了让读者更容易的理解,我一般都会用自己的话来解释它).他的意思就是说,如果类在命名空间内部定义,此时该类的友元函数在使用时可以不加限定.此种情况有点类似于上面说的那种情况,因为友元函数参数中已经有了限定名,所以该函数中可以不需要
.
还是同样举书上一个例子吧
:

13. 如果Using声明在已经有同名且带相同形参表中的函数的作用域中引入函数,Using声明出错.否则Using定义给定名字的另一个重载实例.效果是增大候选函数集合.
  
如果命名空间函数与命名空间所在的作用域中声明的函数同名,就将命名空间成员加到重载集合中.

14. 在命名空间内部声明模板影响着怎样声明模板特化:模板的显示特化必须在定义通用模板的命名空间声明,否则该特化将于它所特化的模板不同名.
  
有两种定义特化的方式:一种是重新打开命名空间并加入特化的含义,可以这样做是因为命名空间定义是不连续的.或者可以用于在命名空间定义外部定义命名空间成员相同的方式来定义特化:使用由命名空间名字限定的模板定义特化
/
  
为了提供命名空间中所定义模板的自己的特化,必须保证在包含原始模板定义的命名空间中定义特化.

15. 多重继承与虚拟继承:
1). 
派生类的对象包括构造和初始化它的所有基类子对象
.
2). 
构造函数初始化式只能控制用于初始化基类的值不能控制基类的构造次序.基类构造函数按照本身的构造函数在类派生列表中的出现次序调用
.
    
构造函数调用次序既不受构造函数初始化列表中出现的基类的影响,也不受基类在构造函数初始化列表中的出现次序的影响.

16. 对于多重继承,派生类的指针或者引用可以转化为其任意基类的指针或者引用.
  
当一个类有多个基类的时候,通过所有直接基类同时进行名字查找.多重继承的派生类有可能从两个或者多个基类成成同名成员,对该成员不加限定的使用是具有二义性的.

17. C++,通过使用虚继承解决这列问题.虚继承是一种机制,类通过虚继承指出它希望共享其虚基类的状态.在虚继承下,对给定虚基类,无论该类在派生层次中作为虚基类出现多少次,只继承一个共享的基类子对象.共享的基类子对象称为虚基类.
  
指定虚派生只影响了从指定了虚基类的类派生类.除了影响派生类自己的对象之外,他也是关于派生类与自己的未来派生类的关系的一个陈述.virtual说明符陈述了后代派生类中共享指定基类的单个实例的愿望
.
  
可以无二义性的直接访问共享虚基类中的成员.同样,如果只沿着一个派生路径重新来自虚基类的成员,则可以直接访问该重定义成员.在非虚派生情况下,两种访问都可能是二义性的
.
  
假定通过多个派生路径继承名为x的成员,则有下面三种可能性
:
  
■ 如果在每个路径中x表示同一虚基类成员,则没有二义性,因为共享该成员的单个实例
.
  
■ 如果在某个路径中x是虚基类的成员,则在另一路径中x是后代派生类的成员,也没有二义性---特定派生类实例的优先级高于共享虚基类实例

  
■ 如果沿每个继承路径x表示后代派生类的不同成员,则该成员的直接访问是二义性的.(像非虚多重继承层次一样,这种二义性最好用在派生类中提供覆盖实例的类来解决.

为了避免在应用于虚基类的时候可能出现的多次初始化基类的情况,我们从具有虚基类的类继承的类对初始化进行特殊处理:由最底层派生类的构造函数初始化虚基类.
  
无论虚基类出现在继承层次中任何地方,总是在构造非虚基类之前构造虚基类.






  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值