C++菱形继承
C++支持多继承,当父类继承自同一个超类,则即为本篇所述的菱形继承。类图如下:
主要参考两个链接:
前一篇提到的VS2003与后文中的实现方式相同,前一篇中还提到了Bjarne方案,不知哪些编译器采用这种方案。所以本文认为前者方案为主流方案,加以阐述。后者方案可视为前者方案的略微改良版。
单类虚继承
代码实现如下:
struct A { virtual foo(); int X;}struct B : virtual public A { virtual foo(); virtual fooB(); int Y;}
其中VPTR指向Virtual Table (VTBL),VBPTR指向Virtual Base Table (VBTBL)。其内容为:
- 第一项为与本类VPTR的偏移。一般VPTR总是存在对象开始处。所以上例为-4字节,取补即为0xFFFFFFFC。如果本类没有虚函数,就没有VPTR,则该值为0。
- 从第二项开始为到各基类的偏移值,有几个基类,表项就有几个。上例中,只有一个基类,且本类成员变量会放在VBPTR之后,所以到基类A的偏移量即为8字节。
注意1: 只有出现了虚拟继承,才会有这样的内存布局,如果没有虚拟继承,则父类VPTR及其成员会挨着子类VPTR放置。
注意2: 此时VPTR中内容和无虚继承时的内容也不同。若B是普通继承A,则B类内存布局为:
菱形继承
对于这样一个菱形继承:
struct A{ A(int v=100):X(v){}; virtual void foo(void){}; int X;};struct B :virtual public A{ B(int v=10):Y(v),A(100){}; virtual void foo(void){}; virtual void fooB(void){} int Y;};struct C : virtual public A{ C(int v=20):Z(v),A(100){}; virtual void foo(void){}; virtual void fooC(void){}; int Z;};struct D : public B, public C{ D(int v =40):B(10),C(20),A(100),L(v){}; virtual void foo(void){}; virtual void fooD(void){}; int L;};
其主要特点为:
- ABCD均包含虚函数,所以每个类都有虚函数表
- 每个类均有成员变量
- 父类BC对祖类采用虚继承
- 子类对两个父类采用普通继承
这应当是最复杂的情况了,当有些类没有虚函数,或者没有成员变量时,情况可能稍许简单些。
A
A类因为没有虚拟继承,所以其内存布局为普通布局:
B / C
BC类均为单类虚继承,如上节所述:
D
D类最复杂
- 本类无覆盖关系的成员函数在最一开始的VPTR中,或认为在B::VPTR中
- 本类成员函数在所有直接父类对象的后面
- 只有一份A对象
- 本类如果覆盖任何父类或祖先类的成员函数会覆盖对应对象的VPTR中的项目。
Error: source type is not polymorphic
当祖先类A没有虚函数的时候,在发生向下转型(子类指针转成父类指针)的时候会出问题。可以查看Memory Layout for Multiple and Virtual Inheritance - Edsko de Vries, January 2006 中Downcasting一节。
考虑这样两个多继承的类:
Bottom* bottom1 = new Bottom();AnotherBottom* bottom2 = new AnotherBottom();Top* top1 = bottom1;Top* top2 = bottom2;Left* left = static_cast<Left*>(top1);
Bottom和AnotherBottom都继承自Left,Left虚继承自Top。现在要把一个Top指针转换成一个Left指针。使用static_cast
会报error: cannot convert from base `Top’ to derived type `Left’ via virtual base `Top’。 因为需要运行时信息,因为所有的VPTR和对象内存布局,都是到运行时才建立的。于是想到可以用dynamic_cast
。
Left* left = dynamic_cast<Left*>(top1);
此时会报另一个错误:error: cannot dynamic_cast `top’ (of type `class Top*’) to type `class Left*’ (source type is not polymorphic)。这是因为Top类没有虚函数,所以此时Bottom和AnotherBottom对象的Top部分没有VPTR。于是编译器也分不清这是一个int还是一个对象。解决的方法就是为Top加一个虚函数。最方便的方法就是加一个空的虚析构函数,如下:
class Top { virtual ~Top(){};}
总结
当发生虚继承的时候,对象内部内存布局会产生大的变化,在引入VBPTR的同时,其各基类对象的排布顺序也发生了变化。
其他参考链接
- 详谈C++虚函数表那回事(多重继承关系, 无虚继承)
- 【C++】c++单继承、多继承、菱形继承内存布局(虚函数表结构) (此篇包含了对《c++虚继承对象的内存布局》一文的阐释