继承与多态虚函数分析

来源:互联网 发布:淘宝最新规则在哪里看 编辑:程序博客网 时间:2024/06/01 23:35

此处举例在VS2010中编译:

分析继承与多态虚函数在内存中的分配布局和分析理解过程,直接以多继承举例:

首先说明虚表的概念:虚表即一个类中存放其虚函数地址的表,这个表的地址则存放在此类内存分布中,即一个_vptr的指针;

虚表基本构建:一个类有多个虚函数时,其按照虚函数在类中出现的先后次序填写虚表,最后在虚表后加上0x00000000.

虚表性质:虚表在一个类对象调用构造函数前已经形成,构造函数只是将虚表地址赋给内存的前四个字节,即为指针_vptr.

1.继承中没有虚函数,举例:

class Base1{public:void Func1(){cout<<"Base1"<<endl;}public:int _a;};class Base2{public:void Func2(){cout<<"Base2"<<endl;}public:int _b;};class Derived:public Base1,public Base2{public:void FuncD(){cout<<"Derived"<<endl;}public:int _d;};void Func(){Derived d;d._a=1;d._b=2;d._d=3;int size=sizeof(d);Base1* p1=&d;Base2* p2=&d;Derived* p3=&d;}
Derived公有继承Base1,Base2,查看内存:


即p1、p3与&d对象地址相同,p2偏移四个字节,d对象总大小为12,则内存分布为:


2.只将以上Base1与Base2函数前加上virtual,变为虚函数,派生类Derived内容不变,查看内存情况:

Base1:

virtual void Func1(){cout<<"Base1"<<endl;}
Base2:

virtual void Func2(){cout<<"Base2"<<endl;}
Derived d;Base1 b1;Base2 b2;

添加两个父类对象,查看内存情况:

b1


b2:

所以当加上virtual关键字后,基类对象各自多增加四个字节,其则为虚表地址_vfptr。再查看继承后的内存分布:


&d地址存储内容如下:


取Base1的_vptr得出虚函数表:


取Base2的_vptr得出虚函数表:


即各自为基类虚函数,并且p1、p3与&d对象地址相同,p2偏移8个字节,d对象总大小为20,所以可以得出内存分布为:


3.在派生类Derived中重写基类中的虚函数Func1()、Func2(),如:

class Derived:public Base1,public Base2{public:virtual void Func1(){cout<<"Derived1"<<endl;}virtual void Func2(){cout<<"Derived2"<<endl;}public:int _d;};
此时在查看内存分布:



虚表为:

Base1


Base2


由上可知此时与第二种情况的内存格局的分布没有任何变化,可是它们的区别又是什么呢?查看以下调用:

若去掉刚才基类和派生类关键字virtual或者派生类没有重写基类函数,用父类指针或引用进行调用此函数,运行结果如下:

class Base1{public:virtual void Func1(){cout<<"Base1"<<endl;}public:int _a;};class Base2{public:virtual void Func2(){cout<<"Base2"<<endl;}public:int _b;};class Derived:public Base1,public Base2{public:/*virtual void Func1(){cout<<"Derived1"<<endl;}virtual void Func2(){cout<<"Derived2"<<endl;}*/public:int _d;};void Fun1(Base1* p1){p1->Func1();}void Fun2(Base2* p2){p2->Func2();}void Fun3(Derived* p3){p3->Func1();p3->Func2();}void Func(){Derived d;Fun1(&d);Fun2(&d);Fun3(&d);Base1 b1;Base2 b2;d._a=1;d._b=2;d._d=3;int size=sizeof(d);Base1* p1=&d;Base2* p2=&d;Derived* p3=&d;}

即输出了基类的函数运行结果,若将注释去掉或加上virtual关键字,运行结果如下:

为派生类重写的运行结果。

这时的原因即为C++的多态的另一类运行时多态,动态连编,在运行时确定指针所指的真正对象类型,调用其函数。

这一过程实现的机制则为:第2种情况和第3对象d内存分布格局相同,调用函数运行结果不同,原因在于创建虚表时函数地址发生改变,此时创建虚表的过程为:Derived先后继承Base1,Base2,所以(1)先创建两个基类虚表,按虚函数在基类中出现的先后顺序填写虚表;(2)基类完成后创建派生类虚表,过程先编译器了解基类虚表格式,再了解派生类对基类哪些虚函数进行了重写;(3)有重写的,则将虚表相同位置的函数地址修改为派生类重写的新的虚函数地址;

(4)若派生类有自己的新的虚函数,则将它加在派生类先继承的基类的虚表后,证明如下:

class Derived:public Base1,public Base2{public://派生类重写两个基类虚函数virtual void Func1(){cout<<"Derived1"<<endl;}virtual void Func2(){cout<<"Derived2"<<endl;}    //派生类自己虚函数virtual void Func3(){cout<<"Derived3"<<endl;}public:int _d;};
查看内存情况

:  
内存格局未变,查看虚函数表:

Base1:


Base2


可知Base1虚表增加了一个函数地址,其就为派生类自己的虚函数。

(5)最后在虚表之后加上0X00000000.

4.若派生类在变为虚拟公有继承基类,如:

class Derived:virtual public Base1,virtual public Base2
查看内存情况:

 
此时&d与p3相同,p1偏移12字节,p2偏移20个字节,总大小为28,在查看各地址:


由上内存监视知:即为派生类Derived虚表;


即为Base1虚表;


即为Base2虚表;


可知第二个内存空间存放偏移表地址,偏移表里存放从当前位置开始与自己、两基类的开始位置偏移字节。

内存分布格局如下:


以上即为以多继承为例剖析多态虚函数的内存布局与分析过程。


1 0