《inside the cpp object model》 阶段性阅读总结(4)
第四章 函数的语义
章前阅读 c++支持,静态,非静态函数成员,虚函数,不同的函数调用,会产生不同的效果。
第一节 不同种类的成员调用
非静态成员函数。在挑选函数实例的时候,是没有花费的,这个是通过内部的转换,将成员函数变为非成员函数而实现的。包括以下三步:参数表中添加this指针,函数体内成员变量增加this指针,重写函数为外部函数并进行名字分解。
名字分解。成员函数的名字被改为唯一的名字,通过整合类名和成员名(与类名结合,我们就可以在子类里访问父类的同名成员了)。分解的模式未知,是和编译器相关的。
虚成员函数。虚函数的调用,会被转义为形如( * ptr->vptr[
index ])( ptr ); 的调用。如果这个类不支持多态,那么通过实例调用函数也会被转义为普通的非静态成员函数。 静态成员函数。由于静态成员函数是被挂在对象外部的。。于是直接分解为普通成员函数,并且不包含this指针,这是最重要的特性。
第二节 虚成员函数
为了支持虚函数的机制,一些形式的运行时类型识别需要被支持。
在这个策略下,一个指针需要包含两方面的信息:
对象的地址
对象类型的编码或者一个地址指向存储该信息的结构体。(目的是寻找正确的调用)
这个解决方案有两个方面的问题。
不论这个程序是不是需要多态,都增加了很大的空间花费
打破了对c语言的兼容
当然,指针是无法存储这些东西的,所以具体的类型的信息会被存储到对象里面(虚函数表里)。
接下来需要解决的的是,区分出哪些类需要这些信息。首先不能通过class或者struct这种关键字去区分。RTTI解决了这个问题,RTTI的出现,使得编译器只需要孤立的考虑哪些类需要RTTI,判断的方法是,查看类是否包含虚函数。
下一个问题是,储存哪些信息,共需要
一个字符串或者数字来代表类型
一个指向某些表的指针,保存程序虚函数的运行时地址(虚函数表)。
子类的同名虚函数地址,会替换父类的虚函数地址,并且和父类同名虚函数的下标一致。这样的好处是,分解完后的调用,只有指针地址是未知的,虚函数指针和下标都是固定的。这样便可支持多态。
多重继承下的虚函数
Base2 *pbase2 = new Derived; 会转义为derived首地址+偏移量(之前的类对象占用的空间)
delete pbase2的时候,又会重新定位为derived首地址。
this指针也需要偏移。编译器的做法是,将vptr转换为结构体,分别存储函数地址和偏移量。
多重继承下,可能会包含多个虚函数表,此时虚函数表的名字会与类名结合到一起,方便调用。
第三章 函数的效率
略
第四章 指向成员函数的指针
对普通的成员函数取地址,获得的是在内存中的实际地址。如果对虚函数取地址,获得的是在虚函数表中的下标。编译器通过&~127来判断当前值是否是虚函数的地址(于是编译器最多可存储128个虚函数)。单继承下这个是可行的。但是多重继承的引入,使得编译器不得不考虑一个方法来破除这个限制。多重继承下,函数地址指向一个结构体。
struct __mptr {
int delta;
int index;
union {
ptrtofunc faddr;
int v_offset;
};
};当index小于0的时候,转义为( *pmf.faddr )( ptr ) ,否则
( * ptr->vptr[ pmf.index ]( ptr ); delta代表this指针的偏移量。
第五节 内联函数
节省了调用和返回值的花费。