C++类的内存结构
简单类
class Base
{
int a;
int b;
public:
void CommonFunction();
};
简单类继承
class DerivedClass: public Base
{
int c;
public:
void DerivedCommonFunction();
};
带有虚函数的类
class Base
{
int a;
int b;
public:
void CommonFunction();
void virtual VirtualFunction();
};
虚函数类继承
没有复写的继承
class DerivedClass: public Base
{
int c;
public:
void DerivedCommonFunction();
void virtual VirtualFunction();
};
复写的继承
class DerivedClass1 : public Base
{
int c;
public:
void DerivedCommonFunction();
void virtual VirtualFunction();
void virtual VirtualFunction2();
};
多重继承
class Base
{
int a;
int b;
public:
void CommonFunction();
void virtual VirtualFunction();
};
class DerivedClass1: public Base
{
int c;
public:
void DerivedCommonFunction();
void virtual VirtualFunction();
};
class DerivedClass2 : public Base
{
int d;
public:
void DerivedCommonFunction();
void virtual VirtualFunction();
};
class DerivedDerivedClass : public DerivedClass1, public DerivedClass2
{
int e;
public:
void DerivedDerivedCommonFunction();
void virtual VirtualFunction();
};
内存分布从父类到子类,依次如下:
Base中有一个虚表指针
DerivedClass1继承了Base,内存排布是先父类后子类。
DerivedClass2的情况是类似于DerivedClass1的。
DerivedDerivedClass,由外向内看,它并列地排布着继承而来的两个父类DerivedClass1与DerivedClass2,还有自身的成员变量e。DerivedClass1包含了它的成员变量c,以及Base,Base有一个0地址偏移的虚表指针,然后是成员变量a和b;DerivedClass2的内存排布类似于DerivedClass1,注意到DerivedClass2里面竟然也有一份Base。
这里有两份虚表了,分别针对DerivedClass1与DerivedClass2,在&DerivedDericedClass_meta下方的数字是首地址偏移量,靠下面的虚表的那个-16表示指向这个虚表的虚指针的内存偏移,这正是DerivedClass2中的{vfptr}在DerivedDerivedClass的内存偏移。
虚继承
class DerivedClass1: virtual public Base
{
int c;
public:
void DerivedCommonFunction();
void virtual VirtualFunction();
};
class DerivedClass2 : virtual public Base
{
int d;
public:
void DerivedCommonFunction();
void virtual VirtualFunction();
};
class DerivedDerivedClass : public DerivedClass1, public DerivedClass2
{
int e;
public:
void DerivedDerivedCommonFunction();
void virtual VirtualFunction();
};
Base类没有变化
DerivedClass1就已经有变化了,原来是先排虚表指针与Base成员变量,vfptr位于0地址偏移处;但现在有两个虚表指针了,一个是vbptr,另一个是vfptr。vbptr是这个DerivedClass1对应的虚表指针,它指向DerivedClass1的虚表vbtable,另一个vfptr是虚基类表对应的虚指针,它指向vftable。
下面列出了两张虚表,第一张表是vbptr指向的表,8表示{vbptr}与{vfptr}的偏移;第二张表是vfptr指向的表,-8指明了这张表所对应的虚指针位于内存的偏移量。
DerivedClass2的内存分布类似于DerivedClass1,同样会有两个虚指针,分别指向两张虚表(第二张是虚基类表)。
下面来仔细看一下DerivedDerivedClass的内存分布,这里面有三个虚指针了,但base却只有一份。第一张虚表是内含DerivedClass1的,20表示它的虚指针{vbptr}离虚基表指针{vfptr}的距离,第二张虚表是内含DerivedClass2的,12表示它的虚指针{vbptr}离虚基表指针{vfptr}的距离,最后一张表是虚基表,-20指明了它对应的虚指针{vfptr}在内存中的偏移。
虚继承的作用是减少了对基类的重复,代价是增加了虚表指针的负担(更多的虚表指针)。
下面总结一下(当基类有虚函数时):
- 每个类都有虚指针(vptr, virtual table pointer)和虚函数表(vtbl, virtual table);
- 如果不是虚继承,那么子类将父类的虚指针继承下来,并指向自身的虚函数表(发生在对象构造时)。有多少个虚函数,虚函数表里面的项就会有多少。多重继承时,可能存在多个的基类虚函数表与虚指针;
- 如果是虚继承,那么子类会有两份虚指针,一份指向自己的虚函数表,另一份指向基类虚函数表,多重继承时基类虚函数表与基类虚指针有且只有一份。