1. 多重继承
多重继承示例代码如下:
class Base1 {
public:
void f0() {}
virtual void f1() {}
int a;
};
class Base2 {
public:
virtual void f2() {}
int b;
};
class Derived : public Base1, public Base2 {
public:
void d() {}
void f2() {} // override Base2::f2()
int c;
};
int main() {
Base2 *b2 = new Base2;
Derived *d = new Derived;
}
class Derived对象有两个vptr,那么有没有可能将这俩vptr合并成一个呢?
答案是不行。这是因为与单继承不同,在多继承中,class Base1
和class Base2
相互独立,它们的虚函数没有顺序关系,即f1和f2有着相同对虚表起始位置的偏移量,所以不可以按照偏移量的顺序排布;并且class Base1
和class Base2
中的成员变量也是无关的,因此基类间也不具有包含关系;这使得class Base1
和class Base2
在class Derived
中必须要处于两个不相交的区域中,同时需要有两个虚指针分别对它们虚函数表索引。
2. top offset
在上节Derived的虚函数表中,有两个top offset
,其值分别为0和-16,那么这个offset起什么作用呢?
在此,先给出结论:将对象从当前这个类型转换为该对象的实际类型的地址偏移量。
仍然以前面的class Derived为例,其虚函数表布局如下:
Vtable for Derived
Derived::_ZTV7Derived: 7u entries
0 (int (*)(...))0
8 (int (*)(...))(& _ZTI7Derived)
16 (int (*)(...))Base1::f1
24 (int (*)(...))Derived::f2
32 (int (*)(...))-16
40 (int (*)(...))(& _ZTI7Derived)
48 (int (*)(...))Derived::_ZThn16_N7Derived2f2Ev
为了能方便理解本节内容,我们不妨将Derived虚函数表认为是 class Base1和class Base2两个类的虚函数表拼接而成 。因为是多重继承,所以编译器将先继承的那个认为是 主基类(primary base) ,因此Derived类的主基类就是class Base1。
在多继承中,当最左边的类中没有虚函数时候,编译器会将第一个有虚函数的基类移到对象的开头,这样对象的开头总是有vptr。
首先看虚函数表的前半部分,如下:
0 (int (*)(...))0
8 (int (*)(...))(& _ZTI7Derived)
16 (int (*)(...))Base1::f1
24 (int (*)(...))Derived::f2
正是因为编译器将class Base1作为Derived的主基类,并将自己的函数加入其中。从上述可以看出offset为0,也就是说Base1类的指针不需要偏移就可以直接访问Derived::f2()
。
接着看虚函数表的下半部分:
32 (int (*)(...))-16
40 (int (*)(...))(& _ZTI7Derived)
48 (int (*)(...))Derived::_ZThn16_N7Derived2f2Ev
偏移值为-16,因为是多重继承,所以class Base1和class Base2类型的指针或者引用都可以指向class Derived对象,那么又是如何调用正确的成员函数呢?
Base2* b2 = new Derived;
b2->f2(); //最终调用Derived::f2();
由于不同的基类起点可能处于不同的位置,因此当需要将它们转化为实际类型时,this指针的偏移量也不相同,且由于多态的特性,b2的实际类型在编译时期是无法确定的;那必然需要一个东西帮助我们在运行时期确定b2的实际类型,这个东西就是offset_to_top
。通过让this指针加上offset_to_top
的偏移量,就可以让this指针指向实际类型的起始地址。
标签:多重,...,int,Derived,C++,Base1,Base2,内存,class From: https://www.cnblogs.com/love-9/p/18086227