虚继承
虚继承解决了菱形继承中最派生类拥有多个间接父类实例的情况。虚继承的派生类的内存布局与普通继承很多不同,主要体现在:
- 虚继承的派生类,如果定义了新的虚函数,则编译器为其生成一个虚函数指针(vptr)以及一张虚函数表。该vptr位于对象内存最前面。(非虚继承时,派生类新的虚函数直接扩展在基类虚函数表的下面。)
- 虚继承的派生类有单独的虚函数表,基类也有单独的虚函数表,两部分之间用一个四个字节的0x00000000来作为分界。
- 虚继承的派生类对象中,含有四字节的虚基表指针。
在C++对象模型中,虚继承而来的派生类会生成一个隐藏的虚基类指针(vbptr),在Microsoft Visual C++中,虚基类表指针总是在虚函数表指针之后,因而,对某个类实例来说,如果它有虚基类指针,那么虚基类指针可能在实例的0字节偏移处(该类没有vptr时,vbptr就处于类实例内存布局的最前面,否则vptr处于类实例内存布局的最前面),也可能在类实例的4字节偏移处。
一个类的虚基类指针指向的虚基类表,与虚函数表一样,虚基类表也由多个条目组成,条目中存放的是偏移值。第一个条目存放虚基类表指针(vbptr)所在地址到该类内存首地址的偏移值,由上面的分析我们知道,这个偏移值为0(类没有vptr)或者-4(类有虚函数,此时有vptr)。虚基类表的第二、第三…个条目依次为该类的最左虚继承父类、次左虚继承父类…的内存地址相对于虚基类表指针的偏移值。我们通过一张图来更好地理解。

代码
class B
{
public:
int ib;
public:
B(int i = 1) :ib(i) {
}
virtual void f() {
cout << "B::f()" ; }
virtual void Bf() {
cout << "B::Bf()" ; }
};
class B1 : virtual public B
{
public:
int ib1;
public:
B1(int i = 100) :ib1(i) {
}
virtual void f() {
cout << "B1::f()" ; }
virtual void f1() {
cout << "B1::f1()" ; }
virtual void Bf1() {
cout << "B1::Bf1()" ; }
};
对象模型

代码演示
typedef void(*Fun)(void);
int main()
{
B1 a;
cout << "B1对象内存大小为:" << sizeof(a) << endl;
//取得B1的虚函数表
cout << "[0]B1::vptr";
cout << "\t地址:" << (int*)(&a) << endl;
//输出虚表B1::vptr中的函数
for (int i = 0; i < 2; ++i)
{
cout << " [" << i << "]";
Fun fun1 = (Fun) * ((int*)*(int*)(&a) + i);
fun1();
cout << "\t地址:\t" << *((int*)*(int*)(&a) + i) << endl;
}
//[1]
cout << "[1]vbptr ";
cout << "\t地址:" << (int*)(&a) + 1 << endl; //虚表指针的地址
//输出虚基类指针条目所指的内容
for (int i = 0; i < 2; i++)
{
cout << " [" << i << "]";
cout << *(int*)

本文详细解析了虚继承的工作原理,包括派生类内存结构的变化、虚函数表与虚基类指针的作用,以及菱形继承下D类对象模型的构成。通过C++代码演示,展示了虚继承如何解决多重继承中的问题和多态实现方式。
1080

被折叠的 条评论
为什么被折叠?



