类大小、虚继承

object size with virtual inheritance

拥有一个虚函数的类对象

class Base1
{
public:
    int base1_1;
    int base1_2;

    virtual void foo(){}
};
int main()
{
    Base1 obj;

    cout << sizeof(Base1) << endl;
    cout << offsetof(Base1, base1_1) << endl;
    cout << offsetof(Base1, base1_2) << endl;
}

输出:
12
4
8

通过在
VS项目属性命令行添加:/d1reportSingleClassLayoutBase1可以看到

1>  class Base1 size(12):
1>      +---
1>   0  | {vfptr}
1>   4  | base1_1
1>   8  | base1_2
1>      +---
1>  
1>  Base1::$vftable@:
1>      | &Base1_meta
1>      |  0
1>   0  | &Base1::foo 
1>  
1>  Base1::foo this adjustor: 0

image

base1_1前面多了一个变量 __vfptr , 其类型为void** ,指向相关的virtual table(图中为vftable)(常说的虚函数表vtable指针), 这说明它是一个void* 指针(注意:不是数组).
其对应的元素[0],类型为void*,值为Base1::foo()这个函数的地址.
则__vfptr的定义伪代码大概如下


void* __fun[1]={&Base1::base1_foo};
const void** __vfptr = &__fun[0]

### 拥有多个虚函数的类对象

“`
class Base1
{
public:
int base1_1;
int base1_2;

virtual void base1_fun1() {}
virtual void base1_fun2() {}

};
“`


1> class Base1 size(12):
1> +---
1> 0 | {vfptr}
1> 4 | base1_1
1> 8 | base1_2
1> +---
1>
1> Base1::$vftable@:
1> | &Base1_meta
1> | 0
1> 0 | &Base1::foo
1> 1 | &Base1::foo2
1>
1> Base1::foo this adjustor: 0
1> Base1::foo2 this adjustor: 0
1>

Base1多了一个虚函数, 类对象大小却依然是12个字节!
image

同样,__vptr指向的函数指针数组中出现了第二个元素,其值为第二个虚函数foo2()的函数地址。
于是现在虚函数指针和虚函数表的伪代码如下:

void* __fun[]={&Base1::foo,&Base1::foo2};
const void** __vfptr = &__fun[0];

通过以上的图表分析:
更加肯定前面我们所描述的:
1. __vfptr只是一个指针, 她指向一个函数指针数组(即: 虚函数表)
2. 增加一个虚函数, 只是简单地向该类对应的虚函数表中增加一项而已, 并不会影响到类对象的大小以及布局情况

此时,如果新增一个类的变量,定义2个类变量,image

  1. obj1和obj2是类的两个变量, 理所当然, 它们的地址是不同的(见 &obj1 和 &obj2).
  2. 虽然obj1和obj2是类的两个变量, 但是:它们的__vfptr的指向却是同一个虚函数表.

于是可以得出结论:
同一个类的不同实例共用同一份虚函数表, 它们都通过一个所谓的虚函数表指针__vfptr(定义为void**类型)指向该虚函数表.

根据以上表示的类对象的内存布局情况:

这里写图片描述
这个清晰的表示出来了虚函数表的关系,
1. 虚函数表是在编译时期为我们创建好的,只存在一份;
2. 定义类对象时,编译器会自动将类对象的__vptr指向这个虚函数表。

单继承且本身不存在虚函数的继承类的内存布局

//定义一个简单继承类

class Base1
{
public:
    int base1_1;
    int base1_2;

    virtual void base1_fun1() {}
    virtual void base1_fun2() {}
};

class Derive1 : public Base1
{
public:
    int derive1_1;
    int derive1_2;
};
1>  class Base1 size(12):
1>      +---
1>   0  | {vfptr}
1>   4  | base1_1
1>   8  | base1_2
1>      +---
1>  
1>  Base1::$vftable@:
1>      | &Base1_meta
1>      |  0
1>   0  | &Base1::base1_fun1 
1>   1  | &Base1::base1_fun2 
1>  
1>  Base1::base1_fun1 this adjustor: 0
1>  Base1::base1_fun2 this adjustor: 0
1>  
1>  class Derive1   size(20):
1>      +---
1>      | +--- (base class Base1)
1>   0  | | {vfptr}
1>   4  | | base1_1
1>   8  | | base1_2
1>      | +---
1>  12  | derive1_1
1>  16  | derive1_2
1>      +---
1>  
1>  Derive1::$vftable@:
1>      | &Derive1_meta
1>      |  0
1>   0  | &Base1::base1_fun1 
1>   1  | &Base1::base1_fun2 

image

如图,基类位于上面,继承类的成员依次在下面定义。
__vfptr展开后,完全是Base1基类的内容: 虚函数表指针+成员变量定义.
并且根据下图可知d1对象内Base1的虚函数表[0][1]的值就是本身base1_fun1和base1_fun2的地址。
image

image


对看完inside C++ Object model第四章对于你理解虚函数虚继承有很大帮助

转载参考:C++中的虚函数(表)实现机制以及用C语言对其进行的模拟实现

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值