C++继承多态下的内存分布
(2012-05-08 23:36:58)面向对象有了一个重要的概念就是对象的实例,对象的实例代表一个具体的对象,故其肯定有一个数据结构保存这实例的数据,这一数据包括变量,接口函数指针,如果是虚函数,则有相应的虚函数指针,其他函数指针不包括。
要讲虚函数机制,必须讲继承,因为只有继承才有虚函数的动态绑定功能,先讲下c++继承对象实例内存分配基础知识:
c++继承分为两种, 普通继承和 虚拟继承(virtual)。具体的继承又根据父类中的函数是否virtual而不同。
下面就单继承分为几种情况阐述:
1.普通继承+父类无virtual函数
2. 普通继承+父类有virtual函数
3.virtual继承
4.多重继承
比如如下一个类:
Class test1() {};
public Virtual
int a;
}
Class test2 extends test1{
int b;
}
Int main(){
}
首先我们看看下:类test1,test2实例大小及其内存分配图:
test1的实例数据大小是:虚函数表指针(4)+iaptr接口指针+int变量大小(4)=12
而test2的实例数据大小是:test1大小+其变量b大小=12+4=16
注意这是上面的提到的虚类继承,子类新增的虚函数不增加子类大小,只是在其虚函数表中体现。
大家注意上面的test1,test2的构造函数,析构函数,fun1,fun2都没加进去。
下面看下实例数据内存分布图:
下面看下其对应的c语言伪代码;
1.
test1.b(Sturct test1 *this ,b){println(“test1”);};
test2.b(Sturct test2 *this ,b){println(“test2:a”);
test2.c(Sturct test2 *this){println(“test2:b}”)}
//上面是虚函数实现
test1.fun1(Sturct test1 *this,){};
test2.fun2(Sturct test2 *this){};
//这个是普通函数,就是上面的,只不过变了名字而已。
2.会生成类对应的结构体:
struct test1{
Stuct Test1_vtbl * vtbl;
Int a;
}test1;
Struct test2{
Stuct Test2_vtbl * vtbl;
Int a;
Int b;
}test2;
3.会生成两个虚函数表结构体:
Struct Test1_vtbl{
}
Struct Test1_vtbl test1_vtb1={test1.a,test1.b,test1.~test1};
//父类test1虚函数表。
Struct Test2_vtbl{
(void *)(test2 *this) c;
}
Struct Test2_vtbl
//子类test2虚函数表。
4.编译器自动生成的一些函数,构造函数,析构函数:
test1.test1(Sturct test1 *this ){
this->vtbl=&test1_vtbl;
}
test1.~test1(Sturct test2 *this){
free(this);
…………..
};
test2.test2(Sturct test2 *this){
test1.test1(this);//调用父类的构造函数,这里也不考虑类型转化,其实编译器会帮我们做好。
this->vtbl=&test2_vtbl;。
}
test2.~test2(Struct test2 *this){
test1.test1(this);
//……….
};
5.main函数对应的代码:
Int main(){
}
对应c伪代码:
Int main(){
}
我们现在分析a->vtbl->b(a,1) 是如何调用到test2.b()函数的。
执行1后,虚函数表是空的,即为null;
执行2
test1_vtbl的,后来又回到test2.test2执行了this->vtbl=&test2_vtbl;
就把test2_vtbl赋给tmp2,然后
Struct test1 *a=tmp;
这个只是指针赋值,可见a还是指向tmp的首地址。
所以a->vtbl->b()执行的是test2.b。
同时a->vtbl->dispose();执行的也是test2的析构函数,为什么呢,因为尽管现在是一个test1对象,但是他本身是一个test2对象,所以结束时要调用其真正的析构函数。
上面也可知道,构造函数不能是虚函数,因为构造函数本身就是赋值虚函数表的,如果自己就是,析构函数必须是析构函数(这个当然还有其他方面考虑).
如果我在fun1() {this->b();b();};
Int main(){
a.
}
其对应于fun1(tes1 this){test1.b();this->vtbl->b();};
如果一个x函数是虚函数,执行x才会调用this虚函数表中的x,如果是this.x,就直接绑定test.x函数了。
本篇文章来源于:开发学院 http://edu.codepub.com