一、菱形继承
那就先从菱形继承开始复习
如下代码:
#include
using namespace std;
class A
{
public:
int _a;
};
class B : public A
{
public:
int _b;
};
class C : public A
{
public:
int _c;
};
class D : public C, public B
{
public:
int _d;
};
int main()
{
D dd;
cout << sizeof(dd) << endl;
dd.B::_a = 1;
dd._b = 3;
dd.C::_a = 2;
dd._c = 4;
dd._d = 5;
B bb;
C cc;
cout << sizeof(bb) << endl;
system("pause");
return 0;
}
B、C中的_a都是来自于A,其实两个_a是同一个,但是因为B、C各自继承,产生了两份,造成了数据冗余。由于D继承于B、C,B、C继承于A,所以D中的_a不知道是来自B还是来自C,于是产生了二义性。
二、菱形虚拟继承
解决上述问题的方法就是将B和C的继承变为虚继承,在子类继承父类时,在访问限定符前加上virtual就可以虚继承。代码如下:
#include
using namespace std;
class A
{
public:
int _a;
};
class B :virtual public A
{
public:
int _b;
};
class C :virtual public A
{
public:
int _c;
};
class D : public C, public B
{
public:
int _d;
};
int main()
{
D dd;
cout << sizeof(dd) << endl;
dd._a = 1;
dd._b = 3;
dd._a = 2;
dd._c = 4;
dd._d = 5;
B bb;
C cc;
cout << sizeof(bb) << endl;
return 0;
}
从上图可以发现对象dd的内存布局与虚继承之前有很大的区别,首先cc对象和bb对象的内存空间中都分别多了一个存储着一个地址的空间,而把它们_a变量放在了成员变量的最底下,使_a成为一个公共的变量。显而易见,加了virtual之后的继承比普通继承的类多了4个字节,而在内存中看到在加了virtual的类中多了一个地址,请看以下分析:
分析二:
由以上分析,发现虚表里的上面的地址存储的是寻找自己的虚表的偏移量
下面的地址存储的是寻找公共的基类的偏移量
C的虚基表里找公共基类的偏移量是20,B的虚基表里找公共基类的偏移量是12,从各自的虚基表往下数
各自的偏移量就可以找到公共的基类A。用一个公共的位置管理公共继承的基类,这样就解决了二义性。
三、带有虚函数的菱形继承
在类的成员函数前加上virtual关键字,则这个成员函数称为虚函数。先看看代码:
#include
using namespace std;
//定义一个可以指向对象里函数的函数指针
typedef void(*func)();
//打印虚函数表
void PrintVtable(int* vtable)
{
printf("vtable:0x%p\n",vtable);
for (size_t i = 0; vtable[i] != 0; ++i)
{
printf("第%d个虚函数地址:0x%p,->", i, vtable[i]);
func f = (func)vtable[i];
f();
}
cout <<"======================================="<< endl;
}
class A
{
public:
int _a;
virtual void func1()
{
cout << "A::func1()" << endl;
}
virtual void func2()
{
cout << "A::func2()" << endl;
}
};
class B :public A
{
public:
int _b;
virtual void func1()
{
cout << "B::func1()" << endl;
}
virtual void func3()
{
cout << "B::func3()" << endl;
}
};
class C :public A
{
public:
int _c;
virtual void func1()
{
cout << "C::func1()" << endl;
}
virtual void func3()
{
cout << "C::func3()" << endl;
}
};
class D : public B,public C
{
public:
int _d;
virtual void func4()
{
cout << "D::func4()" << endl;
}
};
int main()
{
D dd;
dd.B::_a = 1;
dd.C::_a = 2;
dd._b = 3;
dd._c = 4;
dd._d = 5;
//D类里继承B类的虚表
PrintVtable(*(int**)&dd);
system("pause");
return 0;
}
当发生继承时,如果派生类重写了基类的虚函数,那么派生类的对象中会修改基类的虚表,虚表中的函数指针会指向派生类自己重写的函数,如果派生类没有重写基类的虚函数,那么派生类不会改变那个虚函数的指向只是把它继承下来。
四、带有虚函数的菱形虚拟继承
在菱形虚拟继承的基础上加上了虚函数,代码如下:
#include
using namespace std;
//定义一个可以指向对象里函数的函数指针
typedef void(*func)();
//打印虚函数表
void printvtable(int* vtable)
{
cout << "虚表地址>" << vtable << endl;
for (int i = 0; vtable[i] != 0; ++i)
{
printf("第%d个虚函数地址:0x%x,->", i, vtable[i]);
func f = (func)vtable[i];
f();
}
cout << endl;
}
class A
{
public:
int _a;
virtual void func1()
{
cout << "A::func1()" << endl;
}
};
class B :virtual public A
{
public:
int _b;
virtual void func2()
{
cout << "B::func2()" << endl;
}
virtual void func1()
{
cout << "B::func1()" << endl;
}
};
class C :virtual public A
{
public:
int _c;
virtual void func1()
{
cout << "C::func1()" << endl;
}
};
class D : public C,public B
{
public:
int _d;
virtual void func1()
{
cout << "D::func1()" << endl;
}
virtual void func3()
{
cout << "D::func3()" << endl;
}
};
int main()
{
D dd;
B bb;
C cc;
A aa;
cout << sizeof(dd) << endl;
dd._a = 1;
dd._b = 3;
dd._a = 2;
dd._c = 4;
dd._d = 5;
cout << sizeof(bb) << endl;
//D类里继承B类的虚表
cout << "D:bb:0x" << ⅆ
int* vtabledd = (int*)(*(int*)&dd);
printvtable(vtabledd);
//D类里继承C类的虚表
cout << "D:cc:0x" << ((int*)&dd + 3);
int* vtablecc = (int*)(*((int*)&dd + 3));
printvtable(vtablecc);
//D类里继承A类的虚表
cout << "D:aa:0x" << ((int*)&dd + 6);
int* vtableaa = (int*)(*((int*)&dd +6));
printvtable(vtableaa);
return 0;
}
当给bb对象里加了个fun2函数后发现dd的对象模型又多了四个字节,这四个字节是在dd对象中继承的bb的内存空间中多出来的,就是用来存放bb对象自己的虚表地址。
因为aa对象被两个对象所继承,所以aa的虚表被被公用,因此bb和cc对象的虚函数就不能放在他们公共的虚表里,只能创建自己的虚表,把自己的虚函数放在自己的虚表。
1.dd对象继承重写aa对象的虚函数存储在继承来的aa对象的虚函数表里。
2.dd对象里自己的虚函数(非继承来的)存在第一个继承来的对象的虚表里。
3.因为aa对象被两个对象所继承,所以aa的虚表被被公用,因此bb和cc对象的虚函数就不能放在他们公共的虚表里,只能创建自己的虚表,把自己的虚函数放在自己的虚表。