<九>理解虚继承和虚基类
虚基类/抽象类
抽象类:有纯虚函数的类
虚继承
通过修饰继承方式, 如代码2是虚继承,被虚继承的类称为虚基类
虚继承派生类的内存布局方式
先是vbptr => 派生类的数据 =>基类的数据 ,
对比代码1和代码2,发现原本基类数据在前面,派生类数据在后面,但是在虚继承的时候
基类数据方式放到了后面,前面放了vbptr和派生类数据.
vbprt指向的是vbtable ,vbtable中存储的数据是偏移量, 是vbptr指针起始位置到基类的偏移量,见代码2和代码2后面的图片
通过偏移量可以找到基类数据,仔细对比代码1和代码2
vfprt/vbptr
vftabe/vbtable
代码1
class A{
public:
int ma;
protcted:
int mb;
private:
int mc;
}
//B继承 A,
class B : public A{
public:
int md;
potected:
int me;
private:
int mf;
}
代码2 虚继承
#include <iostream>
using namespace std;
class A{
public:
int ma;
protected:
int mb;
private:
int mc;
};
//B继承 A,
class B : virtual public A{
public:
int md;
protected:
int me;
private:
int mf;
};
int main(){
return 0;
}
代码3
#include <iostream>
using namespace std;
class A{
public:
int ma;
virtual void show()
{
}
protected:
int mb;
private:
int mc;
};
//B继承 A,
class B : public A{
public:
int md;
virtual void show()
{
}
protected:
int me;
private:
int mf;
};
int main(){
A *PA=new B();
PA->show();
return 0;
}
代码4
#include <iostream>
using namespace std;
class A{
public:
int ma;
virtual void show()
{
}
protected:
int mb;
private:
int mc;
};
//B继承 A,
class B : virtual public A{
public:
int md;
virtual void show()
{
}
protected:
int me;
private:
int mf;
};
int main(){
A *PA=new B();
PA->show(); // 能正常调用B的show() 方法
delete PA; // 运行报错! 如下图
return 0;
}
vfptr/vbptr vbtable/vbtable 同时出现
当一个类有虚函数,那么就会生成vfptr,vfptr指向vftable,vftable中主要包含RTTI信息和虚函数地址信息
vbptr 专门为派生类从基类中虚继承用得,vbptr指向vbtable,vbtable中主要存储了vbptr到虚基类地址的偏移量
运行报错原因
PA->show();//正常
delete PA ;//运行报错
A *PA=new B(); 用基类指针指向派生类,问题:new B()返回的地址是vbptr起始地址?还是基类vfptr的起始地址?
基类指针指向派生类对象,PA指向的是基类的起始地址,即上图中vfptr起始地址,PA->show()能正常调用,因为
PA指向vfptr起始地址,直接可以将vfptr读取出来,但是释放内存的时候应该从vbptr地址开始释放,所以报错.
代码5
#include <iostream>
using namespace std;
class A {
public:
int ma;
void operator delete(void *p) {
cout <<"A Operator Delete "<< p << endl;
free(p);
}
virtual void show()
{
}
protected:
int mb;
private:
int mc;
};
//B继承 A,
class B : virtual public A {
public:
int md;
void * operator new(size_t size) {
void * p = malloc(size);
cout << "class B operator new malloc Address=" << p << endl;
return p;
}
virtual void show()
{
}
protected:
int me;
private:
int mf;
};
int main() {
A *PA = new B();
cout << PA << endl;
delete PA;
system("pause");
return 0;
}
结合代码5中申请的内存地址,和返回的地址,类的内存结构,偏移量,等信息进行分析了解
如果代码5中改成如下
int main() {
B b;
A *PA = &b;
system("pause");
return 0;
}
b在栈上申请空间就不会有上面释放内存的错误(windows vc编译环境 ).
另外vfptr 是归属 基类还是派生类问题?
如果基类本身有虚函数的,那么vfptr归属基类,如果基类中没有虚函数,派生类有虚函数,那么vfptr归属派生类 如下图
vbtable中的偏移量是vbptr的起始地址到基类的偏移量