一、Template
template的三个主要的讨论方向
- template的声明,也就是说当你声明一个template class、template class member function等时,会发生什么事情。
- 如何”实例化“class object、inline nonmember以及member template functions。这些是”每一个编译单位都会拥有一份实例“的东西。
- 如何”实例化“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++标准才要求它们被实例化。
由使用者来主导”实例化“规则主要有两个原因:
- 空间和时间效率的考虑。因为一个类可能会有很多member functions,但一个class实例并不一定会用到所有的member function。
- 尚未实现的机能。例如:origin的定义需要调用Point的默认构造函数和析构函数,因此只有这两个函数需要被实例化。
实例化这些函数:目前有两种策略:(1)在编译的时候;(2)在链接的时候。
Template的错误报告
在template class中,所有与类型有关的检验,如果牵扯到template参数,都必须延迟到真正的实例化操作发生才进行。
Template中的名称决议法
- scope of the template definition(定义出template的域)
- 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的参数类型“有关而决定的。
- 如果不相关,就使用scope of the template definition来决定name;
- 如果相关,就使用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,其实也就是模板一般化和模板特化,一个用以一般的模板类,另一个用以专注于特定的实例。
二、异常处理
当一个异常发生时,编译系统必须完成以下事情:
- 检验发生throw操作的函数
- 觉得throw操作是否发生在try区段中。
- 若是,编译系统必须把异常类型拿来和每一个catch子句进行比较。
- 如果比较吻合,流程控制应该交到catch子句手中。
- 如果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,在对象空间和执行时间上都需要有一些额外负担:
- 需要额外的空间以存储类型信息,通常是一个指针,指向某个类型信息节点。
- 需要额外的时间以决定执行期的类型(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:
- 如果传回真正的地址,则表示这一对象的动态类型被确认了,一些与类型有关的操作现在可以施行于其上。
- 如果传回0,则表示没有指向任何对象,意味着应该以另一种逻辑施行于这个动态类型未确定的对象身上。
dynamic_cast运算符也适用于引用上,然而对于一个non-type-safe cast,其结果不会与施行于指针的情况 相同。因为引用不可以像指针那样“把自己设为0来表示”no object“”。
Waring:若将一个引用设为0,会引起一个临时对象被产生出来,且该对象的初值为0。
因此,当dynamic_cast施行于一个引用时,会发生下列事情:
- 如果引用真正参考到适当的继承类,downcast会被执行而程序可以继续进行。
- 如果引用并不真正是某一种继承类时,那么,由于不能传回0,因此抛出一个bad_cast 异常。
typeid运算符
typeid运算符,主要用于判断两个对象是否是同一个类。返回值为type_info。
ypeid 返回的是一个class的typeInfo的结构。