虚函数的内存模型

前言

前面文章中我们讨论了虚函数的内存模型,已经了解了包含虚函数的类是通过虚函数表指针、虚函数表和虚函数指针来找到代码段中的函数,本文我们讨论在各种情况下,类在内存中的布局。

无继承关系

我们看下面的代码:

class base1{
public:
    virtual void foo1(){
        std::cout << "call base1 foo1" << std::endl;
    }
    virtual void foo2(){
        std::cout << "call base1 foo2" << std::endl;
    }
    virtual void foo3(){
        std::cout << "call base1 foo3" << std::endl;
    }
};

这是一个无继承的类,按照我们之前讨论的,这个类的内存模型可以用下图表示。这里我们用虚函数指针直接代表虚函数,省去指向代码段的部分。
无继承内存模型实际上虚表中在base::foo3()的下面还有一格,为0表示本虚表为最后一张虚表,为1表示后续还有其他虚表,base::foo1()的前面也还有一个表项,储存了type_info信息,为简化讨论,这两项后续都不画出。
下面的代码可以证明图中的内存模型。

typedef void(*Fun)(void);

int main(){
    base1 d;
    long long* vptr = (long long*)&d;
    for(int i=0;i<3;++i){
        Fun pFun = (Fun)*(((long long*)*vptr)+i); 
	    pFun();
    }
    return 0;
}

运行结果:
在这里插入图片描述

单一继承关系

下面是一段具有单一继承关系的代码:

#include <bits/stdc++.h>

class derive1:public base1{
    virtual void foo3(){
        std::cout << "call derive1 foo3" << std::endl;
    }
    virtual void foo4(){
        std::cout << "call derive1 foo4" << std::endl;
    }
};

类derive1继承了类base并重写了base类的foo3()函数,另外定义了自己的foo4()函数,新增加的foo4()会直接增加在虚表的末尾,其内存布局如下图。
在这里插入图片描述同样的我们用一段代码来证明图中的内存布局。

int main(){
    derive1 d;
    long long* vptr = (long long*)&d;
    for(int i=0;i<4;++i){
        Fun pFun = (Fun)*(((long long*)*vptr)+i); 
	    pFun();
    }
    return 0;
}

运行结果如下:
在这里插入图片描述

多继承

多继承时,实际上会有多张虚表,有几个父类就有几张虚表。

#include <bits/stdc++.h>

class base2{
public:
    virtual void goo1(){
        std::cout << "call base2 goo1" << std::endl;
    }
    virtual void goo2(){
        std::cout << "call base2 goo2" << std::endl;
    }
    virtual void goo3(){
        std::cout << "call base2 goo3" << std::endl;
    }
};

class derive2:public base1,public base2{
    virtual void foo5(){
        std::cout << "call derive2 foo5" << std::endl;
    }
};

int main(){
    derive2 d;
    long long** vptr = (long long**)&d;
    for(int i=0;i<4;++i){
        Fun pFun = (Fun)*(((long long*)vptr[0]+i)); 
	    pFun();
    }
    for(int i=0;i<3;++i){
        Fun pFun = (Fun)*(((long long*)vptr[1]+i));
	    pFun();
    }
    return 0;
}

运行结果如下:
在这里插入图片描述

上面的代码中,类中仅有虚函数表,不存在其他成员变量,当我们在第一个父类中增加成员变量时,上面的代码就不能顺利打印第二个父类的虚函数,比如我们将类base1修改为:

class base1{
public:
    virtual void foo1(){
        std::cout << "call base1 foo1" << std::endl;
    }
    virtual void foo2(){
        std::cout << "call base1 foo2" << std::endl;
    }
    virtual void foo3(){
        std::cout << "call base1 foo3" << std::endl;
    }
private:
    int num;
};

再运行上面的main()函数,输出结果为:
在这里插入图片描述
这是因为多继承实际上的内存布局应该如下图:
在这里插入图片描述此时两个虚函数表指针之间增加了一个成员变量,不能再通过这种方法直接访问第二个虚函数表指针。为了简化讨论,我们后面不使用成员变量。

菱形继承(虚继承)

多继承中,当子类的多个父类有相同的父类时,被称为菱形继承,菱形继承时,为避免存在多个共同父类实例,通常使用虚继承。比如下面的代码中,类derive3的两个父类derive1和derive2都继承自base1,此时如果重写base1中的方法,将会覆盖哪张虚表中的指针?derive3中新增的虚函数又存在于哪张虚表中?

class derive1:virtual public base1{
    virtual void foo3(){
        std::cout << "call derive1 foo3" << std::endl;
    }
    virtual void foo4(){
        std::cout << "call derive1 foo4" << std::endl;
    }
};

class derive2:virtual public base1,virtual public base2{
    virtual void foo5(){
        std::cout << "call derive2 foo5" << std::endl;
    }
};

class derive3:public derive1,public derive2{
    virtual void foo3(){
        std::cout << "call derive3 foo3" << std::endl;
    }
    virtual void foo6(){
        std::cout << "call derive3 foo6" << std::endl;
    }
};

此时derive3中应该有三张虚表,一张继承自derive1,两个来自derive2,我们通过下面的代码来测试结果:

int main(){
    derive3 d;
    long long** vptr = (long long**)&d;
    for(int i=0;i<5;++i){
        Fun pFun = (Fun)*(((long long*)vptr[0]+i)); 
	    pFun();
    }
    
    for(int i=0;i<4;++i){
        Fun pFun = (Fun)*(((long long*)vptr[1]+i));
	    pFun();
    }
    
    for(int i=0;i<3;++i){
        Fun pFun = (Fun)*(((long long*)vptr[2]+i));
	    pFun();
    }

    return 0;
}

运行结果如下:
在这里插入图片描述
可以看到,当我们在derive3中重写foo3()方法时,两张虚表中的虚函数均被重写了,而新增的虚函数增加到了第一张虚表中。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值