文章目录
- 单继承(无虚函数覆盖)
- 单继承(有虚函数覆盖)
- 多继承(无虚函数覆盖)
- 多继承(有虚函数覆盖)
- 菱形继承(有虚函数覆盖)
- 菱形虚拟继承(有虚函数覆盖)
继承关系一般分为: 单继承和 多继承,而多继承的出现就又导致了一种特殊的继承关系 菱形继承,为了解决菱形继承的冗余和二义性,又有了 菱形虚拟继承的解决方案。本篇主要介绍以上四种继承关系的 类对象虚函数内存布局。
tips:本篇中的例程和内存布局均采用编译器visual studio 2022。
单继承(无虚函数覆盖)
假设有如下所示的继承关系:
class Base{
public:
int ibase;
Base():ibase(10){}
virtual void a(){ cout << "Base::a()" << endl; }
virtual void b(){ cout << "Base::b()" << endl; }
};
class Derive : public Base{
public:
int iderive;
Derive():iderive(100){}
virtual void c(){ cout << "Derive::c()" << endl; }
};
在这个继承关系中,派生类并没有重写基类的任何函数。那么,在派生类的的实例中,其内存布局如下:
可以看到以下几点结论:
- 派生类继承了基类的虚函数表;
- 派生类的虚函数进入了虚函数表并且排在基类的虚函数后面。
单继承(有虚函数覆盖)
假设有如下所示的继承关系:
class Base{
public:
int ibase;
Base():ibase(10){}
virtual void a(){ cout << "Base::a()" << endl; }
virtual void b(){ cout << "Base::b()" << endl; }
};
class Derive : public Base{
public:
int iderive;
Derive():iderive(100){}
virtual void a(){ cout << "Derive::a()" << endl; }
virtual void c(){ cout << "Derive::c()" << endl; }
};
在这个继承关系中,派生类中有重写了基类的部分函数。那么,在派生类的的实例中,其内存布局如下:
可以看到以下几点结论:
- 重写后的虚函数放在了虚函数表中基类对应虚函数原来的位置;
- 没有被重写的虚函数在在虚函数表中不变。
多继承(无虚函数覆盖)
假设有如下所示的继承关系:
class Base1{
public:
int ibase1;
Base1():ibase1(11){}
virtual void a(){ cout << "Base1::a()" << endl; }
virtual void b(){ cout << "Base1::b()" << endl; }
};
class Base2{
public:
int ibase2;
Base2():ibase2(22){}
virtual void a(){ cout << "Base2::a()" << endl; }
virtual void b(){ cout << "Base2::b()" << endl; }
};
class Derive : public Base1, public Base2{
public:
int iderive;
Derive():iderive(100){}
virtual void c(){ cout << "Derive::c()" << endl; }
};
在这个继承关系中,派生类中有重写了基类的部分函数。那么,在派生类的的实例中,其内存布局如下:
可以看到以下几点结论:
- 派生类继承了两个基类的两个虚函数表;
- 派生类的两个虚函数表指针分别在自己的区域内指向自己的虚函数表;
- 派生类继承的两个基类按继承顺序在内存中排布。
多继承(有虚函数覆盖)
假设有如下所示的继承关系:
class Base1{
public:
int ibase1;
Base1():ibase1(11){}
virtual void a(){ cout << "Base1::a()" << endl; }
virtual void b(){ cout << "Base1::b()" << endl; }
};
class Base2{
public:
int ibase2;
Base2():ibase2(22){}
virtual void a(){ cout << "Base2::a()" << endl; }
virtual void b(){ cout << "Base2::b()" << endl; }
};
class Derive : public Base1, public Base2{
public:
int iderive;
Derive():iderive(100){}
virtual void a(){ cout << "Derive::a()" << endl; }
virtual void c(){ cout << "Derive::c()" << endl; }
};
在这个继承关系中,派生类中有重写了基类的部分函数。那么,在派生类的的实例中,其内存布局如下:
可以看到以下结论:
派生类重写的虚函数对所有基类都生效。
菱形继承(有虚函数覆盖)
假设有如下所示的继承关系:
class Parent{
public:
int iparent;
Parent():iparent(10){}
virtual void a() { cout << "Parent::a()" << endl; }
virtual void b() { cout << "Parent::b()" << endl; }
virtual void c() { cout << "Parent::c()" << endl; }
};
class Child1 : public Parent{
public:
int ichild1;
Child1():ichild1(111){}
virtual void a() { cout << "Child1::a()" << endl; }
virtual void b_child1() { cout << "Child1::b_child1()" << endl; }
virtual void c_child1() { cout << "Child1::c_child1()" << endl; }
};
class Child2 : public Parent{
public:
int ichild2;
Child2():ichild2(222){}
virtual void a() { cout << "Child2::a()" << endl; }
virtual void b_child2() { cout << "Child2::b_child2()" << endl; }
virtual void c_child2() { cout << "Child2::c_child2()" << endl; }
};
class GrandChild : public Child1, public Child2{
public:
int igrandchild;
GrandChild():igrandchild(1000){}
virtual void a() { cout << "GrandChild::a()" << endl; }
virtual void b_child1() { cout << "GrandChild::b_child1()" << endl; }
virtual void b_child2() { cout << "GrandChild::b_child2()" << endl; }
virtual void c_grandchild() { cout << "GrandChild::c_grandchild()" << endl; }
};
在此继承关系中,两个中间类分别重写了父类的a函数,孙子类又重写了爷爷类的a函数、分别重写了两个父类的b_child2函数和b_child1函数。
可以看到以下结论:
- 菱形继承导致数据冗余(Parent::iparent = 10);
- 当访问Parent::iparent时会产生二义性,需要手动添加Child1::或者Child2::;
- 派生类的虚函数自动排往第一个虚函数表结尾。
菱形虚拟继承(有虚函数覆盖)
为了解决菱形继承产生的数据冗余和二义性,所以C++引入了虚基类的概念,也就是进行虚拟继承,只需在继承的时候再添加一个关键字virtual
。
如下所示继承关系和上个例子一样,仅仅只是添加了关键字virtual
变成了虚拟继承。
class Parent{ ...... };
class Child1 : virtual public Parent{ ...... };
class Child2 : virtual public Parent{ ...... };
class GrandChild : public Child1, public Child2{ ...... };
可以看到以下结论:
- 本来冗余的数据Parent::iparent=10不再冗余,Parent整体部分搬迁到最末尾;
- 虚函数表由两个变为三个,中间类的两个虚函数表中都去掉重复的部分(即继承下来的Parent的虚函数);
- Parent的虚函数由Parent的虚函数表指针单独维护;
- 派生类的虚函数仍自动排往第一个虚函数表;
- 额外多了两个指针__vbptr,该指针指向一个整形数组,元素都为偏移值;
- 根据__vbTable中的偏移值即可找到Parent实例的位置。