1 单继承
使用图片表示如下:
可见以下几个方面:
1)虚函数表在最前面的位置。
2)成员变量根据其继承和声明顺序依次放在后面。
3)在单一的继承中,被overwrite的虚函数在虚函数表中得到了更新。
2 多继承使用图片表示是下面这个样子:
我们可以看到:
1) 每个父类都有自己的虚表。
2) 子类的成员函数被放到了第一个父类的表中。
3) 内存布局中,其父类布局依次按声明顺序排列。
4) 每个父类的虚表中的f()函数都被overwrite成了子类的f()。这样做就是为了解决不同的父类类型的指针指向同一个子类实例,而能够调用到实际的函数。
3 重复继承下面是对于子类实例中的虚函数表的图:
我们可以看见,最顶端的父类B其成员变量存在于B1和B2中,并被D给继承下去了。而在D中,其有B1和B2的实例,于是B的成员在D的实例中存在两份,一份是B1继承而来的,另一份是B2继承而来的。所以,如果我们使用以下语句,则会产生二义性编译错误:
D d;
d.ib = 0; //二义性错误
d.B1::ib = 1; //正确
d.B2::ib = 2; //正确
注意,上面例程中的最后两条语句存取的是两个变量。虽然我们消除了二义性的编译错误,但B类在D中还是有两个实例,这种继承造成了数据的重复,我们叫这种继承为重复继承。重复的基类数据成员可能并不是我们想要的。所以,C++引入了虚基类的概念。
4 虚继承
[http://www.cnblogs.com/cswuyg/archive/2010/08/20/1804113.html]
只要是虚继承,不管是不是多继承,每个有虚函数的直接基类,间接基类(超类)都会有一张虚函数表,使得子类对象中的虚指针按照先直接再间接以及基类继承列表中的顺序 存放(虚指针之间还会存放每个基类的数据成员).同时每个派生类对象会有分别指向每个直接基类的虚基类指针。
最底层派生类内存中存放: 会按照先直接在间接的顺序存放指向每一个有虚函数的各基类(包括直接和间接)的虚函数表的虚指针;虚指针之间会存放每个基类的数据成员;此外,最底层派生类有分别指向各个直接基类的徐基类指针,紧挨各个对应的虚函数表指针存放。
- // __|B1:vfptr |--->|D::f1() |
- // _________ | |_________| |_________|
- // | -4 |<----|--|B1:vbptr | |B1::Bf1()|
- // |_________| | |_________| |_________|
- // | 40 |<----| |B1::ib1 | |D::Df() |
- // |_________| | |_________| |_________|
- // | |B1::cb1 |
- // | |_________| _________
- // _|__|B2:vfptr |--->|D::f2() |
- // _________ | | |_________| |________ |
- // | -4 |<--|-|--|B2:vbptr | |B2::Bf2()|
- // |_________| | | |_________| |_________|
- // | 24 |<--| | |B2::ib2 |
- // |_________| | | |_________|
- // | | |B2::cb2 |
- // | | |_________|
- // | | |D::id |
- // | | |_________|
- // | | |D::cd |
- // | | |_________| _________
- // |_|__|B:vfptr |--->| D::f() |
- // |_________| |_________|
- // |B::ib | | B::Bf() |
- // |_________| |_________|