虚函数表和虚基表

一、虚函数表/虚表(vtable)

如果类中有方法被virtual修饰,则说明这个方法是虚函数,当类中有虚函数声明时,编译器会为该类创建一个虚函数表,将当前的虚函数按照声明次序放入虚函数表中,虚函数表实际上是一个指针数组,这写指针指向实际实现该虚函数的代码地址。

每个对象都包含一个指向该类的虚函数表的指针,这个指针在对象被创建时初始化,通常是作为对象的第一个成员变量

class A{
public:
    virtual void fun1() {cout<<"A::fun1()"}
    virtual void fun2() {cout<<"A::fun2()"}
    int a;
};

此时A对应的对象模型是这样的

1.子类中的虚表

1.1单继承无虚函数覆盖

class A{
public:
    virtual void fun1(){cout<<"A::fun1()"<<endl;}
    virtual void fun2(){cout<<"A::fun2()"<<endl;}
    int a;
};

class B : public A{
public:
    virtual void fun3(){cout<<"B::fun3()"<<endl;}
    virtual void fun4(){cout<<"B::fun4()"<<endl;}
    int b;
};

单继承无虚函数覆盖时,子类只有一个虚函数表,父类虚函数在前,子类虚函数在后,按照声明顺序存放,此时B对应的对象模型如下:

1.2单继承有虚函数覆盖

class A{
public:
    virtual void fun1(){cout<<"A::fun1()"<<endl;}
    virtual void fun2(){cout<<"A::fun2()"<<endl;}
    int a;
};

class B : public A{
public:
    virtual void fun1(){cout<<"B::fun1()"<<endl;}
    virtual void fun3(){cout<<"B::fun3()"<<endl;}
    int b;
};

单继承有虚函数覆盖时,子类只有一个虚函数表,覆盖的虚函数放在虚函数表中继承于父类的位置,新增的虚函数放在继承的父类虚表的后面,此时B对应的对象模型如下:

1.3多继承无虚函数覆盖

class A{
public:
    virtual void fun1(){cout<<"A::fun1()"<<endl;}
    virtual void fun2(){cout<<"A::fun2()"<<endl;}
    int a;
};

class B {
public:
    virtual void fun1(){cout<<"B::fun1()"<<endl;}
    virtual void fun2(){cout<<"B::fun2()"<<endl;}
    int b;
};

class C : public A,public B{
public:
    virtual void fun3(){cout<<"C::fun3()"<<endl;}
    virtual void fun4(){cout<<"C::fun4()"<<endl;}
    int c;
};

多继承无虚函数覆盖时,子类中虚函数表的个数等于直接父类的个数,子类中新增的虚函数放在第一个直接父类的虚函数表末尾,此时C对应的对象模型如下:

1.4多继承有虚函数覆盖

class A{
public:
    virtual void fun1(){cout<<"A::fun1()"<<endl;}
    virtual void fun2(){cout<<"A::fun2()"<<endl;}
    int a;
};

class B {
public:
    virtual void fun1(){cout<<"B::fun1()"<<endl;}
    virtual void fun2(){cout<<"B::fun2()"<<endl;}
    int b;
};

class C : public A,public B{
public:
    virtual void fun1(){cout<<"C::fun1()"<<endl;}
    virtual void fun3(){cout<<"C::fun3()"<<endl;}
    int c;
};

多继承有虚函数覆盖时,子类中虚函数表的个数等于直接父类的个数,子类中重写的虚函数覆盖继承的父类的虚函数,子类中新增的虚函数放在第一个直接父类的虚函数表末尾,按照声明顺序存放,此时C对应的对象模型如下:

二、虚基表

虚基表是编译器为了解决多重继承场景下的菱形继承问题,虚基表(vbtable)通过记录虚基类实例的偏移量来指示派生类如何访问唯一的虚基类实例。当子类通过多继承方式继承多个具有共同基类的父类时,如果不使用虚继承,子类会包含多分共同基类的数据,这会导致数据冗余。而是要虚继承,子类中只会包含一份共同基类的数据。

1.普通继承

class A {
public:
    virtual void fun1(){cout<<"A::fun1()"<<endl;}
    virtual void fun2(){cout<<"A::fun2()"<<endl;}
    int a;
};

class B : public A{
public:
    virtual void fun1(){cout<<"B::fun1()"<<endl;}
    virtual void fun2(){cout<<"B::fun2()"<<endl;}
    int b;
};

class C : public A{
public:
    virtual void fun1(){cout<<"C::fun1()"<<endl;}
    virtual void fun3(){cout<<"C::fun3()"<<endl;}
    int c;
};

class D : public B,public C{
public:
    virtual void fun1(){cout<<"D::fun1()"<<endl;}
    virtual void fun3(){cout<<"D::fun3()"<<endl;}
    int d;
};

普通继承下的菱形继承,会导致D的对象成员里有两份A,导致数据冗余和二义性,此时D对应的对象模型如下:

2.虚继承

class A {
public:
    virtual void fun1(){cout<<"A::fun1()"<<endl;}
    virtual void fun2(){cout<<"A::fun2()"<<endl;}
    int a;
};

class B : virtual public A{
public:
    virtual void fun1(){cout<<"B::fun1()"<<endl;}
    virtual void fun2(){cout<<"B::fun2()"<<endl;}
    int b;
};

class C : virtual public A{
public:
    virtual void fun1(){cout<<"C::fun1()"<<endl;}
    virtual void fun3(){cout<<"C::fun3()"<<endl;}
    int c;
};

class D : public B,public C{
public:
    virtual void fun1(){cout<<"D::fun1()"<<endl;}
    virtual void fun3(){cout<<"D::fun3()"<<endl;}
    int d;
};

虚继承下的菱形继承,只会保留一份A的数据,此时D对应的对象模型如下:

三、类内存大小计算

类只是一种类型定义,它本身是没有大小的。因此,如果用sizeof运算符对一个类型名操作,那得到的是具有该类型实体的大小。

计算一个类对象的大小时的规律:

  • 空类、单一继承的空类、多重继承的空类所占空间大小为1字节
  • 一个类中、虚函数本身、成员函数(包括静态和非静态)和静态成员数据都是不占用类对象的存储空间的
  • 一个对象的大小>=所有非静态成员大小的总和
  • 当类中声明了虚函数(不管是1个还是多个),那么实例化对象时,编译器会自动在对象里创建一个指向该类的虚函数表(vtable)的指针(vptr)
  • 当存在虚继承时,会增加一个指向虚基表(vbtable)的指针(vbptr),多继承则则增加多个
  • 还需考虑编译器下的“补齐”padding的影响,即编译器会插入多余的字节补齐

示例

1.普通继承

class A{

};

class B {
public:
    virtual void fun1(){}
    virtual void fun2(){}
    int b;
};

class C : public A{
public:
    virtual void fun1(){}
    virtual void fun2(){}
    int c;
};

class D : public A,public C{
public:
    virtual void fun1(){}
    virtual void fun3(){}
    int d;
};

class E : public B,public C{
public:
    virtual void fun1(){}
    virtual void fun3(){}
    int e;
};

int main(){
    //32位系统下(32位系统下指针占用4字节,64位系统下指针占用8字节)
    cout<<"A="<<sizeof(A)<<endl; //A=1
    cout<<"B="<<sizeof(B)<<endl; //B=8
    cout<<"C="<<sizeof(C)<<endl; //C=8
    cout<<"D="<<sizeof(D)<<endl; //D=12
    cout<<"E="<<sizeof(E)<<endl; //E=20
    return 0;
}

2.虚继承

class A {
public:
    virtual void fun1(){}
    virtual void fun2(){}
    int a;
};

class B : virtual public A{
public:
    virtual void fun1(){}
    virtual void fun2(){}
    int b;
};

class C : virtual public A{
public:
    virtual void fun1(){}
    virtual void fun3(){}
    int c;
};

class D : public B,public C{
public:
    virtual void fun1(){}
    virtual void fun3(){}
    int d;
};

int main(){
    //32位系统下(32位系统下指针占用4字节,64位系统下指针占用8字节)
    cout<<"A="<<sizeof(A)<<endl; //A=8
    cout<<"B="<<sizeof(B)<<endl; //B=20
    cout<<"C="<<sizeof(C)<<endl; //C=20
    cout<<"D="<<sizeof(D)<<endl; //D=36
    return 0;
}

外部参考:

c++对象内存模型【内存布局】-优快云博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值