C++虚继承和虚函数

C++的虚函数常常搞得人晕头转向,很多同学常常混淆不清,今天我们来解析C++的虚函数

基类和派生类的指向

我们来看这样一段代码

#include<iostream>
using namespace std;

class Base
{
public:
	Base(int a):ma() {cout << "Base:Basee()"<<endl;}
	~Base(){ cout << "Base::~Base()"<<endl;}
protected:
	int ma;
};
class Derive : public Base
{
public:
	Derive(int b):mb(b),Base(b)
	{
		cout << "Derive::Derive()"<<endl;
	}
	~Derive(){cout << "Derive::~Derive()"<<endl;}
priveta:
	int mb;
};

int main()
{
	Base b(10);
	Derive d(20);

//	Derive* pd = &b;派生类指针不能指向基类对象
	Base* pb = &d;

	Base& rb = d;
//	Derive &ed = b;
	return 0;
}

我们可以知道

  • 基类的指针或者引用可以指向或者引用派生类对象
  • 派生类的指针或者引用不可以指向或者引用基类对象

内存布局

#include<iostream>
using namespace std;

class A
{
public:
   A(int a):ma() {cout << "A()"<<endl;}
   ~A(){ cout << "~A()"<<endl;}
protected:
   int ma;
};
class B : public A
{
public:
   B(int b):mb(b),A(b)
   {
   	cout << "B()"<<endl;
   }
   ~B(){cout << "~B()"<<endl;}
protected:
   int mb;
};
class C : public A
{
public:
   C(int c):mc(c),A(a)
   {
   	cout << "B()"<<endl;
   }
   ~B(){cout << "~B()"<<endl;}
protected:
   int mb;
};
class E
{
public:
   E(){}
   ~E(){}
protected:
   int me;
};
class D:public B, public C,public E
{
public:
   D(int d): md(d),B(d),C(d){cout << "D" <<endl;}
}
int main()
{
   D d; 
   return 0;
}

正常的继承类内存布局是这样的
在这里插入图片描述在这里插入图片描述

#include<iostream>
using namespace std;

class A
{
public:
	A(int a):ma() {cout << "A()"<<endl;}
	~A(){ cout << "~A()"<<endl;}
protected:
	int ma;
};
class B : public A
{
public:
	B(int b):mb(b),A(b)
	{
		cout << "B()"<<endl;
	}
	~B(){cout << "~B()"<<endl;}
protected:
	int mb;
};
class C : public A
{
public:
	C(int c):mc(c),A(a)
	{
		cout << "B()"<<endl;
	}
	~B(){cout << "~B()"<<endl;}
protected:
	int mb;
};
class E
{
public:
	E(){}
	~E(){}
protected:
	int me;
};
class D: virtual public B, public C,public E//这里D虚继承B
{
public:
	D(int d): md(d),B(d),C(d){cout << "D" <<endl;}
}
int main()
{
	D d; 
	return 0;
}

但是虚继承下内存布局会发生变化

  1. 布局非虚基类
  2. 布局虚基类
  3. 布局派生类
  4. 将虚基类转移到最下面,原位置填上vbptr
    在这里插入图片描述在这里插入图片描述
    D的内存布局现在就成这个样子,B的内存布局在最下面,在原位置设置了vbptr

构造顺序

虚基类的构造优先级高,所以构造顺序为: A B A C E D

在这里插入图片描述
那如果把B类C类改成这样他的内存布局会是怎么样的呢?

class A
{
public:
	A(int a):ma() {cout << "A()"<<endl;}
	~A(){ cout << "~A()"<<endl;}
protected:
	int ma;
};
class B : virtual public A
{
public:
	B(int b):mb(b),A(b)
	{
		cout << "B()"<<endl;
	}
	~B(){cout << "~B()"<<endl;}
protected:
	int mb;
};
class C : virtual public A
{
public:
	C(int c):mc(c),A(a)
	{
		cout << "B()"<<endl;
	}
	~B(){cout << "~B()"<<endl;}
protected:
	int mb;
};

在这里插入图片描述
这里会发生虚表合并,A的vbptr会合并到C的vbptr,这是C的vbtable下会有两个虚基类数据

#include<iostream>
using namespace std;

class Base
{
public:
	Base(int a):ma() {cout << "Base()"<<endl;}
	virtual void Show(cout << "ma" << ma <<endl;)
	~Base(){ cout << "~Base()"<<endl;}
protected:
	int ma;
};
class Derive : virtual public Base
{
public:
	Derive(int b):mb(b),Base(b){}
protected:
	int mb;
};
int main()
{
	Base* pb = new Derive();
	delete pb;
	return 0;
}

在这里插入图片描述
释放失败
内存布局已经变化 无法找到析构函数

虚函数

为了解决以上的情况,我们要用到虚函数
如果对象有RTTI(运行时类型信息),编译器会使用RTTI,否则推演
在这里插入图片描述
虚函数表在编译期间确定,派生类的vfptr会覆盖基类的vfptr,虚表存放在.rodata段,运行时才把vfptr指向虚表,一个虚表属于一个类而不是一个对象。若一个同名函数不是虚函数,那么不存在虚表中。虚函数调用通过寄存器,运行时才得到函数地址,称作动态绑定,实现动多态。所以当我们用基类指针指向派生类对象时,调用派生类的同名虚函数

哪些不能为虚函数?

  • 函数能取地址
  • 必须依赖对象调用

如何判断动多态?

指针调用时,多为动多态。对象调用时,多为静多态
普通函数在编译期间已经确定地址,存在于符号表,不会加载于内存中。
虚函数存放于.rodata数据段,加载到内存,。通过对象找到虚表,再用寄存器返回入口地址。

指针调用虚函数
1.对象必须完整

继承关系中 析构函数和同名函数的覆盖关系是相同的

这样我们就可以正常的调用基类指针析构了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值