菱形继承
在谈完单继承模型和多继承模型之后,我们来进一步了解更复杂的继承模型——菱形继承。
菱形继承又叫钻石继承。主要结构如图:
菱形继承中含有单继承和多继承。由于class B 和class C都继承了class A中的共有成员_a。那么在class D 多继承时就了存在二义性和数据冗余的问题,虚继承为解决此问题而存在。所以,菱形继承又多出来一种特例——含有虚继承的菱形继承。
首先我们来看一般的菱形继承:
class A
{
public:
virtual void f1()
{
cout << "A::f1()" << endl;
}
virtual void f2()
{
cout << "A::f2()" << endl;
}
public:
int _a;
};
class B:public A
{
public:
virtual void f1()
{
cout << "B::f1()" << endl;
}
virtual void f3()
{
cout << "B::f3()" << endl;
}
public:
int _b;
};
class C:public A
{
public:
virtual void f1()
{
cout << "C::f1()" << endl;
}
virtual void f4()
{
cout << "C::f4()" << endl;
}
public:
int _c;
};
class D:public B,public C
{
public:
virtual void f1()
{
cout << "D::f1()" << endl;
}
virtual void f5()
{
cout << "D::f5()" << endl;
}
public:
int _d;
};
typedef void(*V_FUNC)();//
void PrintVtable(int* vtable)
{
printf("Vtable:0x%p\n", vtable);
int **pptable = (int**)vtable;
for (size_t i = 0; pptable[i] != 0;++i)
{
printf("pptable[%u]:0x%p ", i, pptable[i]);
V_FUNC f = (V_FUNC)pptable[i];//
f();//
}
cout << "------------------------------------------"<<endl ;
}
void Test1()
{
D d;
d.B::_a = 1;
d._b=2;
d.C::_a=3;
d._c = 4;
d._d = 5;
PrintVtable(*((int**)&d));
PrintVtable(*((int**)((char*)&d + sizeof(B))));
//PrintVtable(*((int**)((char*)&d + sizeof(B))));
}
int main()
{
Test1();
system("pause");
return 0;
}
由于调试中的监视窗口无法准确看到虚函数的实际调用。 我们 通过调用一个打印虚表函数来直观看实际调用的虚函数。内存中对虚表地址也有体现。下面图示让你豁然开朗。
趁热打铁,我们来说说菱形虚继承。
首先虚继承的关键字 virtual和虚函数关键字相同,但是没有丝毫联系。
上面我们提到虚继承是为解决问题而生,那么定义格式如下:
class 子类名:virtual 继承方式 类名
{};
在上面例子中应该给B、C冠以虚继承。
class B : virtual public A
{};
class C : virtual public A
{};
void Test1()
{
D d;
d.B::_a = 1;
d._b=2;
d.C::_a=3;
d._c = 4;
d._d = 5;
PrintVtable(*((int**)&d));
PrintVtable(*((int**)((char*)&d + sizeof(B)-sizeof(A))));
PrintVtable(*((int**)((char*)&d + sizeof(D)-sizeof(A))));
}
int main()
{
Test1();
system("pause");
return 0;
}
除了给BC两个类加上关键字之外,测试函数在打印虚表时应注意内存对齐问题。
同样用图示来更直观理解虚函数表。
总结:
我们要清楚这门语言背着我们在干了什么。需要熟悉这门语言,我们就必需要了解C++里面的那些东西,需要我们去了解他后面的内存对象。