c++对象模型 模板 异常处理 执行期类型识别
template
声明
当我们声明一个template class、template class memberfunction等,会发生何事?
成都创新互联公司是专业的明山网站建设公司,明山接单;提供网站设计、成都网站设计,网页设计,网站设计,建网站,PHP网站建设等专业做网站服务;采用PHP框架,可快速的进行明山网站开发网页制作和功能扩展;专业做搜索引擎喜爱的网站,专业的做网站团队,希望更多企业前来合作!
现有如下片段:
template
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 *freeList;
static int chunkSize;
Type _x, _y, _z;
}
声明一个template class,在程序中编译器对其并没有任何反应。换句话说,上述的data member其实并不可用
实例化
- 我们需要显示地指定类型才可使用data member:
Point::Status s;
Point::freeList;
//如下会产生第二个freeList实例
Point::freeList;
//定义指针指向特定实例,程序中啥也没发生。因为编译器不需要知道与该class有关的任何member数据,也没必要初始化template实例,且指针可以为0
Point< float >* ptr = 0;
//reference则不同,它需要实例化,因为reference是需要绑定对象的
const Point& ref = 0;
//扩展
Point temp( (float) 0 );
const Point& ref = temp;
//导致实例化
const Point origin;
对于int和long相同的架构中,以下两个实例化c++standard并未对此进行强制规定应实例化一个还是两,不过大部分编译器都是实例化两个:
Point pi;
Point pi;
- 目前的编译器,面对一个template的处理是完全解析但不做类型检验;只有在实例化操作发生时才做类型检验
- template class内的member functions只有在被使用的时候,才会被实例化
名称决议
-
在c++standard规定了template两个不一样的端,分别是定义template(template定义的文件)的程序端和实例化template(使用的特定例子)的程序端
//定义template端 //只有一个foo()声明位于定义端内 extern double foo( double ); template< class type > class A { public: void do1() { _member = foo( _val ); } type do2() { return foo( _member ); } //... private: int _val; type _member; } //实例化端 //两个foo()声明在实例化端内 extern int foo( int ); template< class type > class A { public: void do1() { _member = foo( _val ); } type do2() { return foo( _member ); } //... private: int _val; type _member; } A
a; //应该调用extern double foo( double )。因为_val的类型与template type参数类型无关 a.do1(); //应调用extern int foo( int )。因为_member与template type参数类型有关 a.do2(); -
template中,对一个nonmenber name的决议结果是根据此name的使用是否与"用以实例化该template的参数类型"有关来决定:
- 若其使用互不相关,则使用定义端来决定name
- 若其使用有关联,则使用实例化端来决定name
-
编译器必须保持两个端上下文(scope contexts):
- 定义端。专注一般的template class
- 实例化端。专注特定实例
异常处理
-
想要支持异常处理,编译器的主要工作是找出catch子句以处理被抛出来的exception
-
异常处理由三个主要组件构成:
- 一个throw子句。它再程序某处发出一个exception,被抛出的exception可为内建类型,亦可为自定类型
- 一个或多个catch子句。每个catch子句都为一个exception handler。表示此子句准备处理某种类型的exception,且在封闭的大括号区段中提供实际的处理程序
- 一个try区段。他被围绕一系列叙述句,这些叙述句可能会引发catch子句起作用
-
当一个exception被抛出,控制权会从函数调用中被释放出,并寻找一个吻合的catch子句。若没有则调用默认处理例程terminate()。当控制权被释放,堆栈中每个函数调用也就被推离,被推离前函数的local class objects的destructor会被调用
Point* mumble { //区域一 Point* pt1, * pt2; //若一个exception于此被抛出,mumble()会被推出堆栈 pt1 = foo(); if( !pt1 ) return 0; //区域二 Point p; //若一个exception于此被抛出,在推出mumble()前需要先调用p的destructor pt2 = foo(); if( !pt2 ) return pt1; }
- 支持以上exception handling,编译器有两种策略:
- 把两个区域(上面两个exception)以个别的"将被摧毁的local objects"链表(在编译器准备妥当)联合起来
- 让两个区域共享同一个链表,该链表在执行期扩大或缩小
- 一个函数可以分为多个区段:
- try以外的区域,且没有active local objects
- try以外的区域,但有一个以上的active local objects需要析构
- try以内的区域
- 支持以上exception handling,编译器有两种策略:
现有一函数含有对一块共享内存的locking和unlocking操作,此时异常处理不保证能正确运行:
void mumble( void* arena )
{
Point* p = new Point;
smLock( arena );
//若此处有exception被抛出,会产生问题
//...
smUnLock( arena );
delete p;
}
//因此我们需要安插default catch子句
void mumble( void* arena )
{
Point* p = new Point;
smLock( arena );
try
{
smLock( arena );
}
catch(...)
{
smUnLock( arena );
delete p;
throw;
}
smUnLock( arena );
delete p;
}
- 处理资源管理,一个办法是将资源需求封装于class object内,并由destructor释放资源
当一个exception发生时,编译系统必须完成以下:
- 检验发生throw操作的函数
- 决定throw操作是否发生在try区段
- 若是,编译系统必须把exception type拿来和每个catch子句比较
- 若比较后吻合,流程控制交给catch子句
- 若throw的发生不在try区段,或没有一个catch子句吻合,那么系统必须摧毁所有active local objects,从堆栈中将目前的函数推离,到下一个函数。再重复2-5
-
编译器必须标示出之前所提到的多个区段,并使它们对执行期的异常处理有所作用。有一个策略是构造program counter-range表格:
program counter内含下一个即将执行的程序指令。为了在一个内含try区段的函数中标示出某区域,可以把program counter的起始值和结束值存储在范围表格
当throw发生时,目前的program counter值拿来与对应的范围表格进行对比,以决定目前作用中的区域是否在一个try区段内。若是,则需找出相关catch子句;若无法处理或exception再次被抛出,目前此函数会从堆栈中被推出,而program counter会被设定为调用端地址,循环将再度开始
-
对于被抛出的exception,编译器必须产生一个类型描述器,对exception的类型进行编码,若为一个derived type,编码内容必须包含其所有base class类型信息(可能被member function捕捉)。编译器还必须为每一个catch子句产生一个类型描述器。执行期的handler会将"被抛出的object的类型描述器"和"每一个catch子句的类型描述器"进行比较,直到吻合或是堆栈已被推离
执行期类型识别
-
downcast有效地把一个base class转换为继承结构的derived class,但其有潜在的危险,因为它遏制了类型系统的作用
-
一个type-safe downcast必须在执行期对指针查询,看其是否指向它所表达的object的真正类型。想要支持type-safe downcast,需要以下要求:
- 需要额外的空间存储类型信息,通常为指针
- 需要额外的时间来决定执行期的类型
-
c++的RTTI机制提供安全的downcast设备,但只对多态类型有效
-
dynamic_cast可以在执行期决定真正的类型
-
type-safe dynamic_cast:
- 若downcast为安全的,则会传回被适当转换过的指针;若不安全,则传回0
- 对一个class指针施行dynamic_cast,会获得true或flase
- 若传回真正的地址,则表示这一object的动态类型被确定,一些与类型有关的操作可以施行于此
- 若传回0,则表示没有指向任何object,应该以另一种逻辑施行于未确定的object
- 对于reference dynamic_cast同样适用,但若为non-type-safe cast,结果和指针不同。因为reference不可以把自己设为0。因此会有另一套方案:
- 若reference真正参考到适当的derived class,downcast被执行
- 若reference并不真正是某种derived class,由于不能传回0,则抛出bad_cast exception
-
dynamic_cast的成本:编译器产出类型描述器
//fct为type派生 typedef type* ptype; typedef fct* pfct; do( ptype pt ) { if( pfct pf = dynamic_cast
(pt) ) { //... } //转换 //virtual table第一个slot内含type_info object地址 ((type_info*)(pt->vptr[0]))->_type_descriptor }
分享标题:c++对象模型 模板 异常处理 执行期类型识别
文章位置:http://scjbc.cn/article/dsoiego.html