索引
C++【对象模型】| 【01】简单了解C++对象模型的布局 C++【对象模型】|【02】构造函数何时才会被编译器自动生成? C++【对象模型】|【03】拷贝构造是如何工作的,何时才会用到呢? C++【对象模型】 | 【04】程序在内部被编译器如何转化? C++【对象模型】 | 【05】类与类之间各种关系下对数据成员的存取、绑定、布局 C++【对象模型】| 【07】构造、析构、拷贝做了哪些事? C++【对象模型】| 【08】类在执行期会处理哪些事呢? C++【对象模型】| 【09】类模板、异常处理及执行期类型识别
三种扩充性质将会影响C++对象:
template、exception handing、runtime type identification;
1、Template
Template:该模板表达式将在编译时期而非执行期被评估,将给程序带来重大的效率提升;
但同时也带来缺点,当与其像依赖的头文件,若产生修改将会重新编译;
1.1 emplate实例行为
【实例化】表示进程将真正的类型和表达式绑定到template相关形式参数上的操作;
当编译器看到templat class 声明时,会如何处理呢?
在实际程序中,它却没有反应;class中的每一个都只能通过该类模板的实例来调用;
template<float>::freeList; // 可通过尖括号中不同类型创建出不同的实例
【指针却在不同类型下不会产生多个实例?】
- 由于指向一个类对象的指针本身不是一个类对象,故该指针不需要与对象相关的任何数据,故不用将其实例化,但C++标准禁止这样处理;
【上述为指针,那么引用呢?】
- 它将会被实例化,否则将会报错;
如何让成员函数需要使用的才进行实例化,编译器不会这样要求,需要使用者来主导,为什么呢?
- 空间上和时间上的考虑,如果类中有大量成员函数,但实际用到只有几个,那么将会耗费大量时间和空间;
- 尚未实现的机能,主要实例化真正用到的成员函数即可;
Point<float> *p = new Point<float>;
需要实例化的有:Point的float类型、new、默认构造三种;
【此类函数在何时将会被实例化呢】
- 如果在编译时,函数将实例化在Point原文件和p所在文件中;
- 如果在链接时,编译器会被重新激活,将template函数实例放在其他文件中;
于template内,什么样的编译器会在编译器处理template声明时被标示出来?
如cfront对template完全解析但不做类型检验,在每一个实例化操作发生时才做类型检验;
1.2 template中名称决议法
extern double foo(double);
template<class type>
class ScopeRules {
public:
void invariant() {
_member = foo(_val);
}
type type_dependent() {
return foo(_member);
}
private:
int _val;
type _member;
};
extern int foo(int);
ScopeRules<int> sr0;
分为两种:①定义出template的程序端、②实例化template程序端;
- ①用以专注于一般的模板类;
- ②用于专注特定的实例;
上述中两个foo()调用,在程序定义前有一个foo声明,而在实例化前有两个foo声明;
当使用sr0.invariant()时,内部的foo将调用foo(double);这是为什么呢?
template中,对一个非成员名称(foo)的决议根据name是否与用以实例化该template的参数类型有关而决定,若不相关,则以①来决定,相关则用②;
而该foo()与实例化该类的参数类型无关【代码注释】;
1.3 成员函数的实例化行为是什么?
目前实例化有两种行为:
- 【编译】,code必须在程序文本文件中;
- 【链接】,辅助工具来引导编译器实例化;
【编译器如何找出函数定义】
- 其中Borland从保护template程序文件中查找,或者要求一种文件用来才存放;
【编译器如何能够只实例化程序中用到的成员函数】
- 模拟链接操作,检测哪一个函数真正需要,在将其实例化;
【编译器如何阻止member definition在多个.o文件中都被实例化呢?】
- 产生多个实例,在从链接器中提供支持,只留下其中一个实例,其余的忽略;
- 或者由使用者来引导模拟链接阶段的实例化策略,决定哪些实例才是需要的;
cfront3.0提供一个由使用者驱动的自动实例化机制;
【directed-instantiation机制】
- 一个程序的原始码被编译时,最初不产生任何实例,但其相关信息将被产生于类文件中;
- 当类文件链接一起时,一个prelinker程序将被执行,用于检查对象文件;
- 对每一个参考到template实例而该实例却没有定义时,prelinker将必要的程序实例化操作指定给特定文件(.ii文件);
- prelinker重新执行编译器,编译.ii文件中将其都实例化;
- 所有的对象文件都被链接成一个可执行文件;
- 该机制的主要成本在于第一次编译时.ii文件的设定时间,及次要的对每一个编译后执行prelinker;
【当一个类被用于几十个文件中,编译器如果确保只有一个虚表实例被产生?】
- 每个虚函数的地址都被置于active classes的虚表中;
- 若取得函数地址,表示虚函数定义必定出现在程序的某个地点,否则链接失败;
- 且改函数只能由一个实例,否则链接失败;
- 故将其放入定义改类的第一个非内联、非纯虚函数的文件中;
- 虚函数被实例化,在类的实例化点之后;
2、异常处理(Exception Handling)
支持EH,则编译器将要找出catch子句,且需要跟踪程序堆栈中每一个函数做作用区域;如果是类EH,则可能要负责其虚构、清理等动作;
EH需要编译器产生的数据结构和执行期的一个exception library:
- 为了维护执行速度,则需要在编译时间构建数据结构,这膨胀程序大小;
- 为了维护程序大小,则需要在执行期建立数据结构,这会影响执行速度;
- 故编译器必须在上面做出抉择;
2.1 EH简介
EH由以下三个组成:
- throw子句,发出一个exception;
- catch子句,没一个子句就是一个EH,处理某种类型的exception;
- try区段;
当一个exception抛出时,控制权会从函数调用中被释放出来,寻找catch子句;
2.2 对EH的支持
当一个exception发生时,系统会执行:
- 检验发生throw操作的函数;
- 决定throw操作是否发生在try区段;
- exception type域catch子句比较;
- 交到catch子句;
如何将exception type和每一个catch type比较
编译器必须为此产生一个类型描述器(如PTTI),对exception的类型进行编码;
3、执行期类型识别
class node {};
class type : public node {};
class fct : public type {};
class gen : public type {};
void simplify_conv_op(type pt) {
fct pf = fct(pt);
}
在cfont2.0前conversion运算符不能被重载,且上述函数(simplify_conv_op)内转换也是安全的;
3.1 保证安全的向下转换操作
缺乏一个保证安全的向下转换,需要额外负担:
- 需要额外的空间以存储类型信息;
- 需要额外的时间以决定执行期的类型;
如何解决向下转化:
- 使用关键词dynamic_cast;
- 通过声明一个或多个虚函数来区别class声明;
3.2 dynamic_cast
该运算符可以在执行期决定真正的类型;
使用它,是取出vptr[0]的类型描述器信息进行转化;
将该运算符使用在reference上
当使用指针时,转换失败将会被设置为0,而引用却不行,若设置为0,将会引起临时性对象被产生出来;
那会如何处理呢?
如果转换失败,则将会抛出一个bad_cast exception;
3.3 typeid
该运算符在执行期查询某个类型的信息;
|