虚继承与虚基类
/*
多重继承:代码的复用,一个子类可以拥有多个父类
虚基类:被虚继承的类
虚继承:继承虚基类
(这个问题是微软vs限定,Linux的g++则不会)
当父类指针指向子类对象时,指向的是子类的父类部分数据的起始位置
1如果子类继承普通父类,内存中先是继承的父类数据,再是自己的数据
即父类指针指向子类内存的首地址
2当子类继承虚基类后,内存中的父类部分移至最底部,子类首地址元素变为vbptr
此时父类指针依旧指向子类内存中父类数据的起始位置,实际位置在子类内存的底部
如果delete释放普通父类指针,因为指针指向子类内存首地址所以可以正常释放
但使用delete释放“虚基类”指针时,因为指针指向的是子类内存的中下部分,所以会出错
当然,如果不使用new的方式生成指针,也就不必delete,自然不会报错了
*/
class A
{
private:
int a;
public:
virtual void func() { cout << "父类的func()" << endl; }
};
class B : virtual public A
{
private:
int b;
int c;
public:
void func() { cout << "子类的func()" << endl; }
};
int main()
{
cout << sizeof(A) << endl; // 8,vfptr指针+int a
cout << sizeof(B) << endl; // 20,vfptr + vbptr + A::a + b + c
A* p1 = new B(); // 在堆上new对象
p1->func();
//delete p1; // 释放内存出错,因为此时父类指针指向的地址不是真正要释放内存的首地址
B b;
A* p2 = &b; // 如果不使用堆,而是指向栈上的子类对象
p2->func(); // 因为不涉及手动delete,系统会自动释放栈上元素,所以也就不会报错了
return 0;
}
菱形继承
/*
菱形继承:可以做更多的代码复用,防止重复调用某个父类
但也会导致子类有多份间接的父类数据保存,可以使用虚继承解决
注意是将D的爷爷辈设置为虚基类,将父辈设置为虚继承
如果不使用虚继承而是直接继承多个父类,此时d对象的内存分布为:
B:: A::a
b
C:: A::a
c
d
共4*5=20字节,并且A及其数据被调用了两次,显然不必要
如果将D继承的父类设置为虚继承(A才是虚基类,BC虚继承A),此时d对象的内存分布为:
B:: vbptr->指向vbtable,第二行值为d内存中B::vbptr到A::a的偏移量5*4=20
b
C:: vbptr->偏移量为3*4=12
c
d
A::a // 将BC中的A::a移动到d的最底部,同时也表示该对象的初始化由D负责
此时对象d的调用就少调用了一次A
*/
class A
{
protected:
int a;
public:
A(int val) :a(val) { cout << "A()" << endl; }
~A() { cout << "~A()" << endl; }
};
class B : virtual public A
{
protected:
int b;
public:
B(int val) :A(val), b(val) { cout << "B()" << endl; }
~B() { cout << "~B()" << endl; }
};
class C : virtual public A
{
protected:
int c;
public:
C(int val) :A(val), c(val) { cout << "C()" << endl; }
~C() { cout << "~C()" << endl; }
};
class D :public B, public C
{
protected:
int d;
public:
// 如果将D继承的BC添加虚继承A,那么BC继承的A就要由D负责初始化,不对A构造会报错
D(int val) :A(val), B(val), C(val), d(val) { cout << "D()" << endl; }
~D() { cout << "~D()" << endl; }
};
int main()
{
D d(10);
A* p1 = &d;
B* p2 = &d;
C* p3 = &d; // 都可以进行正确的动态绑定
return 0;
}