码迷,mamicode.com
首页 > 其他好文 > 详细

站在对象模型的尖端

时间:2020-01-10 20:04:42      阅读:52      评论:0      收藏:0      [点我收藏+]

标签:存储   两种   eid   返回值   特定   list   run   exce   oca   

一、Template

  template的三个主要的讨论方向

  1. template的声明,也就是说当你声明一个template class、template class member function等时,会发生什么事情。
  2. 如何”实例化“class object、inline nonmember以及member template functions。这些是”每一个编译单位都会拥有一份实例“的东西。
  3. 如何”实例化“nonmember、member template functions以及static template class members。这些都是每一个可执行文件中只需要一份实例的东西。

  其中实例化表示进程将真正的类型和表达式绑定到template相关形式参数上。

Template的“实例化”行为

template<class Type>  
class Point  
{  
public:  
    enum Status { unallocated, normalized };  
    Point( Type x = 0.0, Type y = 0.0, Type z = 0.0 );  
    ~Point();  
     
    void* operator new(size_t);  
    void operator delete(void*,size_t);  
private:  
    static Point<Type> *freeList;  
    static int chunkSize;  
    Type _x,_y,_z;  
};  

  当编译器看到template class的时候不会有任何反应,也就是说static data member并不可用,嵌套enum也一样。

  其中,enum Status的真正类型在所有的Point 实例中都一样。但我们依旧只能通过template Point class的某个实例来存取和操作。即我们可以这样写:

Point<float>::Status s;//正确  
Point::Status s;//错误  

//同样freeLsit和chunkSize对程序而言也不可用
Point::freeList;//错误  
Point::chunkSize;//错误  
Point<float>::freeList;//正确  
Point<float>::chunkSize;//正确  

//若是引用
const Point<float> &ref = 0;
//此时,编译器会实例化一个Point的float实例,它会被扩展为一下形式:
//内部扩展  
Point<float> temp(float(0));  //因为引用并不是无物的代名词,0被视为整数,必须被转化为一下类型的一个对象
const Point<float> &ref = temp;  

Point<float> 
//一个class object的定义,不论是由编译器暗中的做(像temp那样),或是由程序员显式的做
const Point<float> origin;  

  都会导致template class 的实例化。

  然而,member function(成员函数)(至少对那些未被使用过的)不应该被实例化,只有在被使用的时候,C++标准才要求它们被实例化。

  由使用者来主导”实例化“规则主要有两个原因:

  1. 空间和时间效率的考虑。因为一个类可能会有很多member functions,但一个class实例并不一定会用到所有的member function。
  2. 尚未实现的机能。例如:origin的定义需要调用Point的默认构造函数和析构函数,因此只有这两个函数需要被实例化。

  实例化这些函数:目前有两种策略:(1)在编译的时候;(2)在链接的时候。

Template的错误报告

  在template class中,所有与类型有关的检验,如果牵扯到template参数,都必须延迟到真正的实例化操作发生才进行。

Template中的名称决议法

  1. scope of the template definition(定义出template的域)
  2. scope of the template instantiation(实例化template的域)
//scope of the template definition  
  
extern double foo( double );  
  
template<class type>  
class ScopeRules  
{  
public:  
    void invariant()  
     {  
            _member = foo(_val);  
     }  
     type type_dependent()  
     {  
            return foo(_member);//书中此处我觉得有问题,因为type的具体值是什么还不知道  
              //那么如果类型不匹配就无法成功调用foo()函数。还请网友指点一下  
     }  
private:  
     int _val;  
     type _member;  
}; 

//scope of template instantiation  
extern int foo( int );  
ScopeRules<int> sr0;  

  此时如果有以下调用:

sr0.invariant();  
//那么在invariant()中调用的究竟是哪个foo()函数实例?答案是
extern double foo( double );  

  因为:

  在Template中,对于一个nonmember name(非成员名称)的决议结果,是根据这个name的使用是否  与”用以实例化该template的参数类型“有关而决定的。

  1. 如果不相关,就使用scope of the template definition来决定name;
  2. 如果相关,就使用scope of template instantiation来决定name;

 如果此时有如下调用:

sr0.type_dependent(); 

  那么此时会调用scope of template instantiation中声明的foo()函数,而在该例子中,共有两个foo()函  数,且此例的_member类型为int,所以调用

extern int foo( int );  
//如果是
ScopeRules< double > sr0;  
//那么就会调用
extern double foo( double );

  不管如何演变,都是由“scope of template instantiation”来决定。

  总结如下:

scope of template definition//用以专注于一般的template class  
scope of template instantiation//用以专注于特定的实例  

Member function的实例化行为

  template functions的实例化:

  目前有两个策略,一个是编译时期策略,另一个是链接时期策略。

  但这两个策略都有一个共同的缺点:当template实例被产生出来时,有时候会大量增加编译时间。

总结

  因为模板类型不确定,所以对一个模板类型的变量赋初值可能会是错误的。因为模板类型不确定,所以并不是所有运算符都会支持。模板最后应该以分号结束。因为,在模板类中,所有关于类型的检查会延迟到实例化之后才会发生。

  一个编译器要保持两个scope contexts,其实也就是模板一般化和模板特化,一个用以一般的模板类,另一个用以专注于特定的实例。

二、异常处理

  当一个异常发生时,编译系统必须完成以下事情:

  1. 检验发生throw操作的函数
  2. 觉得throw操作是否发生在try区段中。
  3. 若是,编译系统必须把异常类型拿来和每一个catch子句进行比较。
  4. 如果比较吻合,流程控制应该交到catch子句手中。
  5. 如果throw的发生并不在try区段中,或没有一个catch子句吻合,那么系统必须(a)摧毁所有已构造的局部对象,(b)从堆栈中将木目前的函数“unwind”(解除)掉。(c)进行到程序堆栈的下一个函数中去,然后重复上述步骤2~5。

  当一个异常被抛出时,异常对象会被产生出来并通常放置在相同形式的异常数据堆栈中。从throw端传给catch子句的,是异常对象的地址、类型描述器(或是一个函数指针,该函数会传回与该异常类型有关的类型描述器对象)以及可能会有的异常对象描述器。

  c++的实现中,正常执行时,在每一个函数被推离堆栈之前,函数的 local class objects 的 destructor会被调用。

  如果exception抛出时,exception之前的local object 会析构,后面的代码不执行,所以才需要那些什么auto_ptr什么的包装一层,也就是在前面声明一些auto_ptr,即使遇到exception,auto_ptr 的析构函数也会被调用。

三、执行期类型识别

(Type-safe Downcast)保证安全的向下转换操作

  一个保证安全的向下转换操作必须在执行期对指针有所查询,看看它是否指向它所展现的对象的真正类型。因此,要想支持type-safe downcast,在对象空间和执行时间上都需要有一些额外负担:

  1. 需要额外的空间以存储类型信息,通常是一个指针,指向某个类型信息节点。
  2. 需要额外的时间以决定执行期的类型(run type),因为,这需要在执行期才能决定。

  C++的RTTI(运行时类型识别)机制提供了一个安全的downcast设备,但只对那些展现多态(也就是使用继承和动态绑定)的类型有效。

  通过声明一个或多个虚拟函数来区别class声明来分辨出这些类型。

  在C++中,一个具备多态性质的class,正是内含继承而来(或直接声明)的虚拟函数。

  c++ 的 RTTI(Run time type identification)执行期类型识别,有的编译器是在virtual table里的一个指针指向具体的 type_info结构来实现的。

  假如产生一个exVetex异常(exVetex继承自exPoint),下面两种写法有些不同:

//1.
catch(exPoint p){
    throw; //继续抛出的是exVetex,p是一个临时对象
}

//2.
catch(exPoint &p){
    throw;//继续抛出的是裁剪后的exPoint
}

  只有在一个catch子句评估完毕并且知道它不会再抛出exception之后,真正的exception object才会被销毁。

引用不是指针

  程序执行中对一个class指针类型施以dynamic_cast运算符,会获得true或false:

  1. 如果传回真正的地址,则表示这一对象的动态类型被确认了,一些与类型有关的操作现在可以施行于其上。
  2. 如果传回0,则表示没有指向任何对象,意味着应该以另一种逻辑施行于这个动态类型未确定的对象身上。

  dynamic_cast运算符也适用于引用上,然而对于一个non-type-safe cast,其结果不会与施行于指针的情况  相同。因为引用不可以像指针那样“把自己设为0来表示”no object“”。

  Waring:若将一个引用设为0,会引起一个临时对象被产生出来,且该对象的初值为0。

  因此,当dynamic_cast施行于一个引用时,会发生下列事情:

  1. 如果引用真正参考到适当的继承类,downcast会被执行而程序可以继续进行。
  2. 如果引用并不真正是某一种继承类时,那么,由于不能传回0,因此抛出一个bad_cast 异常。

typeid运算符

  typeid运算符,主要用于判断两个对象是否是同一个类。返回值为type_info。

  ypeid 返回的是一个class的typeInfo的结构。

站在对象模型的尖端

标签:存储   两种   eid   返回值   特定   list   run   exce   oca   

原文地址:https://www.cnblogs.com/tianzeng/p/12177852.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!