C++对象模型之七 模板 异常 RTTI笔记
下面关于模板的三个问题:
1 模板的声明时会发生什么
2 如何实例化出类对象内联成员
3 如何实例化出成员,模板成员函数,模板静态成员.
Template <class T>
Class point
{
public:
enum Status { unallocated, normalized };
point (T x=0.0,T y=0.0, T z=0.0);
~point ();
void * operator new ( size_t );
void * opertaor deleted (voide * , size_t );
private:
static point<T> * freeList;
static int ChunSize;
T _x, _y, _z;
};
当编译器看到template class 声明时,它什么反应也没有,上述的静态数据成员,嵌套枚举 不可用. 虽然enum status 的真正的类型在所有的point实例中都一样.但是它们必须通过模板point类的实例来存取和操作.
Point <float >:: Status S; // OK
Point ::Status S;//Error
指针: const point < float > * ptr = 0; 不会实例化;
引用: const point < float > & ref = 0; 会实例化
è point < float > temp ( floa t (0 ) ); const point < float > & ref = temp;
模板的错误报告: 所有的语法错误被检测出来,但不做类型检查.
模板中的名称解决方式
extern double foo ( double ); //类S的定义
template < class T>
class S
{
public:
void invariant () { member = foo ( val); }
T dependent() { return foo ( member) ;}
private:
int val;
T member;
};
extren int foo (int );
S<int> S1;
S1.invariant();
S1.dependent();
模板中对于成员名称的解决结果时根据这个名称的使用是否与”用以实例化出该模板的参数类型有关” 而决定.没有关就采用类的定义来解决, 有关就采用实例化的参数类型.
因此: S1.invariant();中的foo 调用的 extern double foo ( double ); 因为void invariant () { member = foo ( val); }中的val 是int val; 类型无关.而 S1.dependent(); 中的foo 调用的 extern int foo ( int );因为T dependent() { return foo ( member) ;}中的member 是T member; 类型有关.
成员函数的实例化
只有在成员函数使用时后C++标准才要它们被实例化出来.但是并不精确遵循.愿意如下:
1 空间和时间的考虑. 如果类中有100个成员函数,但你的程序只针对某个类型的7个函数,那么其他的193个函数都要被实例化将花费大量的时间和空间.
2 尚未实现的机制,并不是一个模板实例化出来的所有类型就一定能够完整支持一组成员函数所需的所有运算符.
Point < float > *ptr = new point < float >;
用到了 void * operator new ( size_t ); point (T x=0.0,T y=0.0, T z=0.0);
目前编译器有两个策略: 1是编译时期程序代码必须在程序文本文件中备妥当,2 是连接时用工具引导编译器实例化行为.
1 编译器如何找出函数的定义?
1是包含在模板程序文本中,2 要求一个文件命名规则.
2 编译器如何只实例化程序中用到的成员函数?
1 根本忽略,把一个已经实例化的类所有的成员函数都生产出来. 2仿真连接操作: 检测哪个函数真正需要.
3 编译器如何阻止成员定义在多个obj文件中都被实例化?
1 产生多个实例在连接时候只留下一个. 2 由使用者来引导”仿真连接阶段”的实例.
Borland 支持所有问题的第一中方法.
异常处理
一般而言异常处理机制需要与编译器所产生的数据结构以及执行期的一个异常库精密合作.
编译器在程序速度和大小方面做出选择
1 为了维持运行速度,编译器可以在编译时期建立起用于的数据结构.但编译器可以忽略这些结构直到异常
2 为了维护程序大小,编译器可以在执行期建立起用于的数据结构, 但编译器只有在必要的时候才建立数据结构.
异常可以不程序划分为多个区域: try block 以外 和try block以内以及整个区域.
void mumble ( void * arena )
{
point *p = new point ;
try
{
smlock ( arena );
}
catch (…)
{
smunlock ( arena );
delete p;
throw ;
}
smunlock (arena);
delete p;
}
为什么 point *p = new point ;不在try block以内? 因为它之前没有什么要释放的东西.虽然它自己也会抛出异常,但是它会自己处理调资源释放的问题.
支持异常会使成员对象或基类子对象的类的构造函数复杂化. 比如: 类X有成员对象 A,B,C.它们都有一对的构造和析构函数. 但A的构造函数抛出异常,那么A,B,C的析构不用调用. 但 B的构造函数抛出异常,那么A的析构要调用,C的析构不用调用. 一样的是 point3d *ptr = new point3d [512]; 假如第100个元素抛出异常,那么前99个要调用析构,后面的就不用调用.
当一个异常发生时,编译器要完成以下事情:
1 检验throw 操作函数
2 决定 throw 操作是否发生在try 区域中
2.1 如果是 编译器把异常类型和每一个catch比较
2.2 如果比较吻合,流程控制权交到catch中
3 如果 throw 操作不发生在try 区域中 或者没有一个catch吻合 那么系统会做 1 释放所有本地存在的对象, 2 从堆栈中将当前的函数展开掉 3 进行到程序堆栈中的下一个函数,然后重复2-3
决定 throw 操作是否发生在try 区域中 函数中会生成区域统计表
如果是 编译器把异常类型和每一个catch比较 系统会采用类型描述器(RTTI)
当异常被丢出时,异常对象会放在相同形式的异常数据堆栈中,从throw传给catch 的是 异常对象地址和类型描述器.一般catch( ex &p) 基本上采用引用 如果是对象,有能发生切割.
对象大小
没有异常
有异常
百分比
Borland
86.822
89.510
3%
Microsoft
60.146
67.071
13%
Symantec
69.786
74.826
8%
执行速度
Borland
78
83
6%
Microsoft
83
87
5%
Symantec
94
96
4%
运行期类型识别(RTTI)
向下兼容:
RTTI是后来引进的,并没有使用新的关键字,而是如果声明的虚函数就会有RTTI。
动态类型转换:dynamic_cast可以在运行期决定类型,如果成功将传回指针否则是0 比静态转换成本要贵的多,但是也安全多了。Dynamic_cast < Type > ( &pt) 如果是引用失败后返回的是bad_cast异常。
TYPEID:
使用它可以同样达到引用的目的
if ( typeid (rt)== typeid (fct))
fct &rf=static_cast<fct&>(rf);
typeid 转会来的const 引用 类型为type_info : bool type_info ::operator==(const type_info&) cosnt;
class type_info
{
public:
virtual ~type_info();
bool operator==(const type_info&) const;
bool operator!=(const type_info&) const;
bool before (const type_info &) const;
const char *name() const;
private:
type_info (const type_infor&);
type_info & operator = (const type_info &);
};
RTTI 适合与多态 type 适合于普通类和一般类型。
曾牧暗鲨 && 大白鲨 2003-8-19