不同继承体系下带虚函数的对象模型
单继承
class B
{
public:
virtual void Fun1()
{
cout << "B::Fun1()" << endl;
}
virtual void Fun2()
{
cout << "B::Fun2()" << endl;
}
virtual void Fun3()
{
cout << "B::Fun3()" << endl;
}
public:
int _b;
};
class D:public B
{
public:
virtual void Fun2()
{
cout << "D::Fun2()" << endl;
}
virtual void Fun3()
{
cout << "D::Fun3()" << endl;
}
public:
int _d;
};
typedef void(*PVFT)();
void PrintFun(B& b)
{
PVFT *pVFT = (PVFT*)(*(int*)&b);
while (*pVFT)
{
(*pVFT)();
pVFT++;
}
cout << endl;
}
int main()
{
D d;
PrintFun(d);
}
多继承
代码代码:
class B1
{
public:
virtual void Fun1()
{
cout << "B1::Fun1()" << endl;
}
virtual void Fun2()
{
cout << "B1::Fun2()" << endl;
}
public:
int _b1;
};
class B2
{
public:
virtual void Fun1()
{
cout << "B2::Fun1()" << endl;
}
virtual void Fun2()
{
cout << "B2::Fun2()" << endl;
}
public:
int _b2;
};
class D:public B1,public B2
{
public:
virtual void Fun1()
{
cout << "D::Fun1()" << endl;
}
virtual void Fun3()
{
cout << "D::Fun3()" << endl;
}
public:
int _d;
};
int main()
{
D d;
cout << sizeof(d) << endl;
d._b1 = 1;
d._b2 = 2;
d._d = 3;
B1& b1 = d;
PrintFun(b1);
B2& b2 = d;
PrintFun(b2);
}
我们先定义了两个基类B1和B2,在这两个基类中分别定义Fun1和Fun2两个虚函数,再定义一个派生类D公有继承B1和B2,在D中对Fun1进行重写,并再新增一个虚函数Fun3,输出D类对象的大小为20
通过画图分析我们发现派生类对象中存在两张虚表,分别对应基类B1和B2,派生类中重写虚函数Fun1,对两张虚表中的Fun1都进行了覆盖,在派生类中新增的虚函数Fun3被加入到了先继承的基类B1的虚表中,系统这样做的目的是在调用Fun3时方便查找,只需取该对象的前四个字节拿到虚表指针即可访问。
当我们将基类B1中的Fun1和Fun2去掉virtual关键字,声明为一般的成员函数,而不是虚函数,再运行该程序,发现派生类对象d的大小变成了16个字节,还是通过画图我们来see一see它的对象模型:
变成了这样一个造型,虽然我们在派生类中先继承B1,在继承B2,但是系统还是将B2对应的部分放在派生类对象的最前面,原因可想而知,带有虚函数的类中前四个字节一般放的都是虚表指针,方便查找虚表,因此可以认为虚基类的优先级比一般基类的优先级要高。
菱形继承
class B
{
public:
virtual void Fun1()
{
cout << "B::Fun1()" << endl;
}
virtual void Fun2()
{
cout << "B::Fun2()" << endl;
}
public:
int _b;
};
class C1:public B
{
public:
virtual void Fun1()
{
cout << "C1::Fun1()" << endl;
}
virtual void Fun3()
{
cout << "C1::Fun3()" << endl;
}
public:
int _c1;
};
class C2 :public B
{
public:
virtual void Fun2()
{
cout << "C2::Fun2()" << endl;
}
virtual void Fun4()
{
cout << "C2::Fun4()" << endl;
}
public:
int _c2;
};
class D :public C1,public C2
{
public:
virtual void Fun1()
{
cout << "D::Fun1()" << endl;
}
virtual void Fun3()
{
cout << "D::Fun3()" << endl;
}
virtual void Fun4()
{
cout << "D::Fun4()" << endl;
}
virtual void Fun5()
{
cout << "D::Fun5()" << endl;
}
public:
int _d;
};
typedef void(*PVFT)();
void PrintFun(C1& c)
{
PVFT *pVFT = (PVFT*)(*(int*)&c);
while (*pVFT)
{
(*pVFT)();
pVFT++;
}
cout << endl;
}
void PrintFun(C2& c)
{
PVFT *pVFT = (PVFT*)(*(int*)&c);
while (*pVFT)
{
(*pVFT)();
pVFT++;
}
cout << endl;
}
int main()
{
D d;
cout << sizeof(d) << endl;
d.C1::_b = 0;
d._c1 = 1;
d.C2::_b = 2;
d._c2 = 3;
d._d = 4;
C1& c1 = d;
PrintFun(c1);
C2& c2 = d;
PrintFun(c2);
}
在该菱形继承中,C1将基类B中的Fun1重写,并新增加了虚函数Fun3;C2将基类B中的Fun2重写,并增加了Fun4,派生类D继承C1和C2,并重写Fun1、3、4,增加Fun5,因此两张虚表都被改写,Fun5被加入第一张虚表,目的是在调用Fun5时方便查找。
虚拟继承
class B
{
public:
virtual void Fun1()
{
cout << "B::Fun1()" << endl;
}
virtual void Fun2()
{
cout << "B::Fun2()" << endl;
}
public:
int _b;
};
class D :virtual public B
{
public:
int _d;
};
int main()
{
D d;
cout << sizeof(d) << endl;
d._b = 1;
d._d = 2;
PrintFun(d);
}
在基类B中我们定义了两个虚函数Fun1和Fun2以及一个成员变量_b,派生类D虚拟继承基类B,D中只定义了一个成员变量_d,我们知道虚拟继承的对象模型是倒立的,基类在下,派生类在上,前四个字节放的是偏移量表的地址,该表中前四个字节是相对派生类对象起始地址的偏移量,后四个字节是基类的相对该位置的偏移量,通过该表可以找到基类在该对象中对应的位置。由于派生类D未对基类中的虚函数进行重写,因此基类虚表无改动,对应下图
若在派生类中对Fun1重写,并且加上虚函数Fun3,其对象模型会是什么样嘞
class D :virtual public B
{
public:
virtual void Fun1()
{
cout << "C1::Fun1()" << endl;
}
virtual void Fun3()
{
cout << "C1::Fun3()" << endl;
}
public:
int _d;
};
重写Fun1后,基类B对应的虚表被改写,该对象模型对应的前四个字节放的也是一个虚表的地址,在这个虚表中只有派生类中新加入的虚函数;下面四个字节是偏移量表的地址。
菱形虚拟继承
菱形虚拟继承解决了菱形继承中的二义性问题,我们接下来看一看加入虚函数后它的对象模型是怎么样的。
代码与上面菱形继承的代码相同,只是在C1和C2类继承基类B时加上了virtual关键字。
int main()
{
D d;
cout << sizeof(d) << endl;
d._c1 = 1;
d._c2 = 2;
d._d = 3;
d._b = 4;
C1& c1 = d;
PrintFun(c1);
C2& c2 = d;
PrintFun(c2);
B& b = d;
PrintFun(b);
}
其对象模型见下图,基类B在整个对象模型中只有一份,并且存放在最下面,由于先继承自C1类,因此C1类对应部分放在上面,其中前四个字节为C1的虚表地址,派生类D对Fun3重写,因此调用D中的Fun3,D中新添加的Fun5也被放入C1的虚表中,方便查找,接着四个字节是偏移量表的地址,该表前四个字节是相对于C1所对应部分起始位置的偏移量,后四个字节是基类B对应部分相对该位置的偏移量,再下面四个字节存放的是C1类的成员变量_c1。C2和C1类似,基类B中前四个字节是B对应的虚表地址,该虚表中Fun1先在C1中被重写,再在D中被重写,Fun2在C2中被重写,最后被改写成了下面这样的造型
带虚函数的各种继承其对象模型就分析到这里,over!