C++中虚函数详解二

来源:互联网 发布:降低音量软件旧版 编辑:程序博客网 时间:2024/05/29 03:35

文章出处:

http://blog.csdn.net/shift_wwx/article/details/78622643


前言:

《C++中虚函数详解一》详细解释了在单一继承中虚函数的使用,这一文来详细解释下在多重继承中虚函数的使用。


1、多重继承重写父类的虚函数

先来看实例代码:

class Base1 {public:Base1() : value(1) { cout << "Base1 constructor..." << endl; }~Base1() { cout << "Base1 destructor..." << endl; }virtual void f1() { cout << "base1 function f1..." << endl; }virtual void f2() { cout << "base1 function f2..." << endl; }virtual void f3() { cout << "base1 function f3..." << endl; }    int value;};class Base2 {public:    Base2() : value(2) { cout << "Base2 constructor..." << endl; }    ~Base2() { cout << "Base2 destructor..." << endl; }    virtual void f1() { cout << "Base2 function f1..." << endl; }    virtual void f2() { cout << "Base2 function f2..." << endl; }    virtual void f3() { cout << "Base2 function f3..." << endl; }    int value;};class FromBase : public Base1, public Base2 {public:FromBase() : value(3) { cout << "FromBase constructor..." << endl; }~FromBase() { cout << "FromBase destructor..." << endl; }virtual void f2() { cout << "FromBase function f2..." << endl; }    int value;};
定义了两个base的类作为父类,定义FromBase类作为子类。其中f2()是重写的函数。

测试代码如下:

void Test2::testBase1(long **ptl) {    Base1 *p;    p = (Base1*)ptl;    cout << "address base1 is: " << p << endl;    cout << "value vptr is: = 0x" << uppercase << hex << *ptl << endl;    cout << "address base1 value is:" << &(p->value) << endl;    long *vptr = *ptl;    Fun fun = (Fun)*(vptr + 0);    fun();    fun = (Fun)*(vptr + 1);    fun();    fun = (Fun)*(vptr + 2);    fun();}void Test2::testBase2(long **ptl) {    Base2 *p;    p = (Base2*)ptl;    cout << "address base2 is: " << p << endl;    cout << "value vptr is: = 0x" << uppercase << hex << *ptl << endl;    cout << "address base2 value is:" << &(p->value) << endl;    long *vptr = (long*)*ptl;    Fun fun = (Fun)*(vptr + 0);    fun();    fun = (Fun)*(vptr + 1);    fun();    fun = (Fun)*(vptr + 2);    fun();}void Test2::testFromBase(long **ptl) {    cout << "address FromBase is: " << ptl << endl;    cout << "value vptr is: = 0x" << uppercase << hex << *ptl << endl;    long *vptr = (long*)*ptl;    Fun fun = (Fun)*(vptr + 0);    fun();    fun = (Fun)*(vptr + 1);    fun();    fun = (Fun)*(vptr + 2);    fun();}void Test2::test1() {    long **ptl = NULL;Base1 base1;    Base2 base2;    FromBase fbase;    ptl = (long**)(&base1);    testBase1(ptl);    cout << endl << endl;    ptl = (long**)(&base2);    testBase2(ptl);    cout << endl << endl;    ptl = (long**)(&fbase);    testFromBase(ptl);    cout << "test1 end..." << endl;}
运行结果如下:


对于Base1和Base2对象的内存分布,这里不做重复的解释,详细看《C++中虚函数详解一》,主要看下两个类对象的虚函数表情况:

对象base1的虚函数表内存中分布:


所以虚函数表结构表示为:


对象base2的虚函数表内存分布:


所以虚函数表的结构表示为:


最后来看一下fbse对象的内存分布:


从这里看到有两张虚函数表,每个虚函数头指针后面还有value的值。网上很多文章可能会误导人,都以为虚函数表都是挨着的,其实不是的。

暂时跳过对象的内存分配,下面会总结。先来看两张虚函数表:


对比base1和base2的虚函数表,可以发现只要是子类中有可以重写父类的函数,在虚函数表中都会进行覆盖。

这也就是解释了为什么子类对象通过父类的指针能找到子类的函数。例如,

Base1 *p1 = &fbase;//其实指的是0x2AFC58Base2 *p2 = &fbase;//其实指的是0x2AFC60

这样通过上面的指针找到虚函数的头指针,然后找到对应的虚函数表,这样能找到子类的重写的函数了。

这就是多态性的原理,通过基类指针能找到不同对象的所要处理的函数,实现了同样的消息实现不一样的结果。


另外,对于多重继承第一张虚函数表最后并没有出现之前单一继承中出现的0,而多重继承最后一张虚函数表最后出现的是0。这个有个疑问,谁知道为什么吗?跟编译器有关系吗?


2、多重继承没有父类的虚函数
把子类FromBase中的虚函数改一下:

class FromBase : public Base1, public Base2 {public:FromBase() : value(3) { cout << "FromBase constructor..." << endl; }~FromBase() { cout << "FromBase destructor..." << endl; }virtual void fb() { cout << "FromBase function fb..." << endl; }    int value;};
运行结果这里不再重复,主要来看对象fbase的内存分配:


显然还是两张表,来看下两张虚函数表:


最后发现子类自身的虚函数会放在第一张虚函数表中。


总结:

  • 不同的父类中的虚函数存放在不同的表中;
  • 如果父类中有虚函数被子类重写,那么不管父类中虚函数访问权限是publib、private、protected,都会在虚函数表中进行覆盖(基类指针可以访问派生类private函数);
  • 父类自身的虚函数会放在第一张虚函数表中。


3、对象的内存分配

这是第1点中遗留了一个问题,来看下第1点中对象fbase的内存分布。


从中可以看出其实对象的内存分布应该是:

父类的虚函数表头指针4个字节(看编译系统,32系统是4个字节)+成员变量 + ... + 自身的成员变量

做成表的形式如下:



至此,对象的虚函数使用问题基本是解释完了,通过上面的总结出总的内存分布图如下:


注意:

  • 如果对象是通过new创建的,那么对象是分配在堆上面的,就如图所示。但是,如果对象只是普通创建,那应该是分配在栈上面,那这个图就需要稍微改动下。
  • 虚函数表中存放的都是函数指针,函数段还是位于内存中的代码段。




原创粉丝点击