虚拟继承和虚函数对比
目的不同
虚拟继承用来动态访问数据(节约存储空间或保证数据地址唯一),虚函数用来动态访问方法。
虚表的访问方式
如果 A 类有虚函数,那么 A 类已经有 vptr、是多态类,通过 A 类指针/引用访问虚函数则需要查找虚表。
如果 A 类是普通类并且被虚拟继承,那么 vptr 并不会放到 A 类中,通过 A 类指针/引用访问虚函数也不会查表。
考虑 A *pa = new B;
,其中 B 类虚拟继承于 A 类,通过 A 类指针 pa
访问到的成员就是真正要找的 A 中的成员,因为从 B 类指针转换到 A 类指针时编译器已经正确处理好了指针偏移问题,从而不需要担心没有 vptr 导致访问不到正确的成员。
考虑菱形继承的情况:
struct A { /* ... */ };
struct B : virtual A { /* ... */ };
struct C : virtual A { /* ... */ };
struct D: B, C { /* ... */ };
从指向 D 类真实对象的 B 类指针访问来自 A 中的成员,就会用上 B 类的 vptr 查找偏置信息。
和非虚拟对应物相比的额外代价
以 Itanium C++ ABI 为例。
虚函数:首先读 vptr,再用编译时确定的下标去访表,相当于多访存两次才取得了函数指针。
虚拟继承得到的数据成员:首先读 vptr,然后从 vptr 减去一部分(减多少是在编译时确定下来的)得到新指针,访问这个位置指向的数据可以得到虚基类子对象在本类中的偏移量。而具体继承来的基类子对象的偏移是在编译时确定,所以虚拟继承相当于多访存了两次。可以参考 📌vtable 中的这张图片: