我们首先参阅如下的代码:
class Person {
public:
int pFlag = 2;
Person() {
printf("Person \r\n");
}
virtual ~Person() {
printf(" ~Person \r\n");
}
virtual void vSayPerson() {
printf("Person \r\n");
}
void nSayPerson() {
printf("nSayPerson \r\n");
}
};
class XH :public Person {
public:
int xhFlag = 4;
virtual ~XH() {
printf("~XH \r\n");
}
XH() {
printf("XH \r\n");
}
virtual void vSayXH() {
printf("vSayXH \r\n");
}
virtual void vSayXH2() {
printf("vSayXH2 \r\n");
}
void nSayXH() {
printf("nSayXH \r\n");
}
};
int main()
{
{
XH* pXh = new XH();
pXh->nSayPerson();
pXh->vSayPerson();
pXh->vSayXH2();
pXh->nSayXH();
delete pXh;
}
return 0;
}
Debug编译后的汇编代码
我们跟进到构造函数中:
我们按照上面的顺序逐个分析
我们首先构建出整个内存图:
构造函数要先调父类的初始化函数:
因为子类会用到父类资源,比如子类获取父类的变量
先初始化虚表指针在调用属性初始化和方法体:
因为构造函数会有可能调用虚函数
先初始化属性在调用方法体:
方法体可能会获取属性
我们首先虚表的赋值代码:
我们接下来看下Person这个类的初始化函数
你会差异的发现父类构造也会填入自己虚表,完成父类构造的后,子类又会覆盖写入这个虚表地址。
这样会有什么关系和异常呢?假设子类重写父类的虚函数,在父类构造函数调用虚函数只会调用自己的函数而不是子类的。
我们看下XH虚表地址的交叉引用信息
我们观察下XH析构函数:
为啥需要在自己析构函数中再次给自己的虚表赋值呢?为了解答这个答案我们首先才看~Person析构
在析构对象流程,首先释放子类的所有子类资源,在释放父类所有资源。因为子类资源被释放了,如果调用到父类时虚表没有还原父类的虚表,那么父类析构中有调用虚函数的可能会引起意外的异常。因为指向的函数是一个释放资源的子类函数。
我们最后看看几个虚函数和非虚函数的调用
pXh->nSayPerson();
pXh->vSayPerson();
首先我们要知道的是XH的虚表中第二项就是vSayPerson函数地址,第一项是析构函数代理函数地址。
现在你应该对虚函数的调用有一定的认识了吧。现在你应该举一反三回答出为啥析构函数为啥一定要是虚函数了。。。
我们现在重写Person虚函数看看XH的虚表会怎么样.
class XH :public Person {
public:
int xhFlag = 4;
virtual ~XH() {
printf("~XH \r\n");
}
XH() {
printf("XH \r\n");
}
virtual void vSayXH() {
printf("vSayXH \r\n");
}
virtual void vSayXH2() {
printf("vSayXH2 \r\n");
}
void nSayXH() {
printf("nSayXH \r\n");
}
//重写
virtual void vSayPerson() {
printf("XH vSayPerson\r\n");
}
};
class BaseClass {
public:
int baseFlag = -1;
int fill[32] = {0};
BaseClass(int flag) {
baseFlag = flag;
printf("BaseClass \r\n");
}
virtual ~BaseClass() {
printf(" ~BaseClass \r\n");
}
};
class Person : public BaseClass {
public:
int pFlag = 2;
Person() :BaseClass(2) {
printf("Person \r\n");
}
virtual ~Person() {
printf(" ~Person \r\n");
}
virtual void vSayPerson() {
printf("Person \r\n");
}
void nSayPerson() {
printf("nSayPerson \r\n");
}
};
class Female : public BaseClass {
public:
int fFlag = 3;
Female() :BaseClass(3) {
printf("Female \r\n");
}
virtual ~Female() {
printf(" ~Female \r\n");
}
virtual void vSayFemale() {
printf("vSayFemale \r\n");
}
void nSayFemale() {
printf("nSayFemale \r\n");
}
};
class XM :public Person, public Female {
public:
virtual void vSayFemale() {
printf("vSayFemale \r\n");
}
virtual ~XM() {
printf("~XM \r\n");
}
XM() {
printf("XM \r\n");
}
virtual void sayXm() {
printf("sayXm \r\n");
}
};
int main()
{
XM* pXm = new XM();
//280
printf("%d \r\n", sizeof XM);
delete pXm;
return 0;
}
我们可以看到多继承下XM输出类大小是280字节,我们改用虚继承
class BaseClass {
public:
int baseFlag = -1;
int fill[32] = {0};
BaseClass(int flag) {
baseFlag = flag;
printf("BaseClass \r\n");
}
virtual ~BaseClass() {
printf(" ~BaseClass \r\n");
}
};
class Person : virtual public BaseClass {
public:
int pFlag = 2;
Person() :BaseClass(2) {
printf("Person \r\n");
}
virtual ~Person() {
printf(" ~Person \r\n");
}
virtual void vSayPerson() {
printf("Person \r\n");
}
void nSayPerson() {
printf("nSayPerson \r\n");
}
};
class Female :virtual public BaseClass {
public:
int fFlag = 3;
Female() :BaseClass(3) {
printf("Female \r\n");
}
virtual ~Female() {
printf(" ~Female \r\n");
}
virtual void vSayFemale() {
printf("vSayFemale \r\n");
}
void nSayFemale() {
printf("nSayFemale \r\n");
}
};
class XM :public Person, public Female {
public:
virtual void vSayFemale() {
printf("vSayFemale \r\n");
}
virtual ~XM() {
printf("~XM \r\n");
}
XM():BaseClass(0x123) {
printf("XM \r\n");
}
virtual void sayXm() {
printf("sayXm \r\n");
}
};
int main()
{
XM* pXm = new XM();
//280
printf("%d \r\n", sizeof XM);
delete pXm;
return 0;
}
可见虚继承减少部分内存,我们首先研究下分非虚继承下的XM内存结构
我们直接查看XM的构造函数
析构方法
我们知道虚继承可减少共同父类占用空间,比如本例中XM类会有两个BaseClass类,因此我们会想280减去一个Base类大小
就是虚继承后的大小。具体数值为 144=280-136
,但是我们通过允许后发现实际内存是160大小。
//..其他代码略,这里是虚继承的代码
int main()
{
XM* pXm = new XM();
printf("sizeof XM %d \r\n", sizeof XM);
printf("sizeof Female %d \r\n", sizeof Female);
printf("sizeof Person %d \r\n", sizeof Person);
printf("sizeof BaseClass %d \r\n", sizeof BaseClass);
delete pXm;
return 0;
}
我们很明显发现Female和Person大小变大了8字节.
为了研究这个问题们修改以下代码首先构造一个Female
int main()
{
Female* pXm = new Female();
printf("sizeof XM %d \r\n", sizeof XM);
printf("sizeof Female %d \r\n", sizeof Female);
printf("sizeof Person %d \r\n", sizeof Person);
printf("sizeof BaseClass %d \r\n", sizeof BaseClass);
delete pXm;
return 0;
}
我们这里为方便理解直接给出虚继承内存结构:
IDA PRO 查看构造函数你会发现这个代码会有分支结构,有可能不会调用虚基类初始化函数
我们最后再看看XM这个类的内存结构体: