C++-----深度探索C++对象模型-第四章-Function语意学(二)
1、多态对象有某种形式执行期类型判断法,多态其实就是使用一个基类指针寻址出一个派生类对象的意思。
2、识别一个class是否支持多态,唯一的方法就是看它是否有任何虚函数。
1)编译期,找到虚函数表,每个类对象被安插一个由编译器内部产生的指针,指向这个表格。每一个虚函数被指派一个表格索引值。
2)执行期,激活虚函数。
3、一个类只会有一个虚函数表,每一个表内含其对应的类对象激活虚函数的函数实例的地址,这些激活的虚函数包括:
1)这一个类所定义的函数实例,它会改写(overriding)一个可能存在的基类虚函数实例。
2)继承自基类的函数实例,这是在派生类中决定不改写虚函数时才会出现的情况。
3)一个pure_virtual_called()函数实例,它既可以扮演pure_virtual_function(纯虚函数)的空间保卫者角色,可以当做执行期异常处理函数。
4、当一个派生类继承自一个基类,一共有三种可能性:
1)它可以继承基类所声明的虚函数实例,准确说是,该函数实例的地址会被拷贝到派生类的虚函数表的相对应的位置。
2)它可以使用自己的函数实例,这表示它自己的函数实例地址必须放在对应的slot中。
3)他可以加入一个新的虚函数,这个时候虚函数表的尺寸会增大一个slot,而新的函数实例地址会被放进该solt中。
5、单一继承中,假设基类有一个函数x()在虚函数表的4位置,那么派生类的这个函数也是在4这个位置。
6、现在假设有一个基类指针,指向一个虚函数实例,如下:
ptr->z();
那么在编译期能够知道的东西是,经过ptr可以取到该对象的虚函数表,尽管你不知道ptr所指的真正对象。编译器可以知道每一个z函数在虚函数表中的位置,尽管不知道是哪一个z函数实例被调用。通过这些信息就可以把上面的代码转换成如下形式:
(*ptr->vptr[4])(ptr);
这一转化中,vptr表示编译器所安插的指针,指向虚函数表。4表示z函数被指派的slot号,唯一一个在执行期知道的东西是slot4所指的到底是哪一个z函数实例。
7、在单一继承模型中,虚函数机制十分良好,不但有效率而且很容易塑造出来模型,但是在多重继承和虚拟继承中对虚函数的支持就不那么美好了。
8、在多重继承中支持虚函数,其复杂度围绕着在第二个及后继的基类身上,以及必须在执行期调整this指针。
9、如下代码
class Base1{
public:
Base1();
virtual ~Base1();
virtual void speakClearly();
virtual Base1 *clone() const;
protected:
float data_Base1;
};
class Base2{
public:
Base2();
virtual ~Base2();
virtual void mumble();
virtual Base2 *clone() const;
protected:
float data_Base2;
};
class Derived:public Base1,public Base2{
public:
Derived();
virtual ~Derived();
virtual Derived *clone() const;
protected:
float data_Derived;
};
派生类支持虚函数的困难程度主要落在Base2对象身上,就上面的程序而言,有三个方面需要注意,1)虚析构函数。2)被继承下来的Base::mumble()函数 。3)一组clone函数实例。
//将一个派生类对象地址给基类指针
Base2 *pbase2= new Derived;
//编译时期会产生以下代码
Derived *temp=new Derived;
Base2 *pbase2=temp ? temp++size(Aase1):0;
如果没有这样的调整,指针的任何非多态运用都会失败。
10、一般规则是,经由指向第二或后继之基类的指针或引用来调用派生类虚函数,其所连带的必要的this指针调整操作必须在执行期完成。也就是说,偏移量的大小,以及把偏移量加到this指针上头的一小段程序代码必须由编译器在某个地方插入。
11、thunk技术,就是一小段代码,用来1)用适当的offset值调整this指针。2)跳转到虚函数去。
12、在多重继承下,一个派生类内含n-1个额外的虚函数表,n表示上一层基类的个数,因此单一继承不会有额外的虚函数表。上面的例子,Derived会产生两个虚函数表,一个主要实例,与Base1(最左端基类)共享。一个次要实例,与Base2(第二个基类)有关。
13、针对每一个虚函数表,派生类对象中有对应的虚函数指针,虚函数指针会在构造函数中被设立初值。用以支持一个类拥有多个虚函数表的传统方法是将每一个表以外部对象的形式产生出来并给独一无二的名字。于是当你将一个派生类对象地址给一个Base1或Derived时,被处理的虚函数表是主要表格vtbl_Derived,而当你将一个Derived对象地址指定给一个Base2指针是,被处理的虚函数表是次要表格。
14、有三种情况,第二或后继的基类会影响对虚函数的支持。
1)通过一个指向第二个基类的指针,调用派生类虚函数。指向的次要表格。
Base2 *ptr=new Derived;
//调用Derived::~Derived()
//ptr必须向后调整sizeof(Base1)个byte
delete ptr;
2)通过一个指向派生类的指针调用第二个基类中继承来的虚函数,在此情况下派生类指针必须再次调整,指向第二个基类对象。指向主要表格。
Derived *pder=new Derived;
//调用Base2::mumble()
//pder必须向前调整sizeof(Base1)个byte
pder->mumble();
3)第三种情况发生在一个语言扩充性质下,允许一个虚函数的返回值类型有所变化,可能是基类类型,也可可能是公有的派生类类型,通过clone()函数可以来说明,clone函数的派生类版本传回一个派生类指针,改写了两个基类版本,当通过指向第二个基类的指针调用clone()时,this指针的offset问题就诞生了
Base2 *pb1=new Derived;
//调用Derived * Derived::clone()
//返回值必须调整。以指向Base2 对象
Base2 *pb2=pb1->clone;
当进行pb->clone()时,pb1会被调整指向Derived对象的起始地址,于是clone()的派生类版本会被调用,它会传回一个指针,指向一个新的派生类对象,该对象的地址被指定给pb2之前必须先经过调整,以指向Base2对象。

浙公网安备 33010602011771号