229-C++继承与多态(理解虚基类和虚继承)

多重继承

好处是代码的复用,相当于一个派生类有多个基类。

  • 既从A继承又从B继承,C是从A,B多继承而来;
  • C有2个基类:A和B;
  • C可以把A和B的成员都继承而来,复用起来

虚基类

拥有纯虚函数的类称为 虚基类

被虚继承的类称作虚基类,vbptr和vbtable
在这里插入图片描述
virtual的用法:

  • 1、修饰成员方法是虚函数
  • 2、可以修饰继承方式,是虚继承。被虚继承的类,称作虚基类

在这里插入图片描述
A被B虚继承,所以A是虚基类
我们打开命令提示符来看看
在这里插入图片描述在这里插入图片描述
A被B虚继承,所以A是虚基类
在这里插入图片描述
A被虚继承,A本身内存布局没有变化

我们看看B的内存布局:
在这里插入图片描述
在这里插入图片描述
B大小:12,vbptr+mb+ma

原本应该最上面放ma,然后放mb,但是现在多了一个vbptr,那么A被B继承的虚基类的数据ma跑哪里去了?跑到派生类最后面来了。

vbptr指向的是vbtable,vbtable第一行是0,第二行是这个虚基类指针vbptr到虚基类数据的偏移量,偏移量是按字节算的,从vbptr偏移4个字节到mb,然后偏移4个字节到ma,所以vbtable的第二行存放的是8。

在这里插入图片描述
当我们在分析时,看到是虚继承,考虑派生类的内存空间。

我们可以先不考虑虚继承的情况:
在这里插入图片描述
当我们这个基类被虚继承以后,基类就成为虚基类了,虚基类的数据一定要搬到我们派生类内存的最后面,然后在刚才的地方添加vbptr(virtual base ptr):
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
vbptr指向是vbtable,一个类对应一个虚基类表。
在这里插入图片描述

vbtable有2行:

  • 第一行存的是向上的偏移量,因为vbptr在派生类对象内存的起始部分,所以向上的偏移量是0。

  • 第二行存的是向下的偏移量,原来的基类数据在最上面放着,现在搬到了最下面。ma被挪地方了,vbtable通过虚函数指针告诉,ma移到了哪个位置,偏移了多少字节,存放的就是vbptr离虚基类数据的偏移量。

这个vbtable也是编译时生成,运行时期被放在数据段
一个类型定义的多个对象的vbptr指向的是同一个类型的vbtable
在这里插入图片描述

举例

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
查看类的布局:
在这里插入图片描述

C++继承和多态,有2个指针和2个表。
在这里插入图片描述

  • 都能够一个类中有虚函数,类生成的对象就有vfptr指向的vftable,vftable里面主要放的就是RTTI指向的是运行时的RTTI信息,另外一个就是虚函数地址;
  • vbptr:专门在派生类中,从基类 虚继承而来,vbtable第二行放的是vbptr离虚基类数据在派生类内村中的偏移量!

应用拓展

当我们的虚基类指针和虚函数指针在一块的时候,并不影响多态的使用!
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

最终调用到了B的func,但是系统提示堆操作有问题。

为什么会出现这样的问题???

  • 当我们的虚函数指针,虚函数表,虚基类指针, 虚基类表在一起结合的时候,调用起来没问题,但是delete的时候出错了。

我们先分析一下:

B的内存布局:
在这里插入图片描述
vbptr指向一个vbtable,vfptr指向一个vftable,当我们在主函数new了一个B对象
它的内存布局就是上图所示。
在这里插入图片描述
但是当我们把B的内存地址给指针p的时候,是把vbptr给p还是把vfptr给p呢?
在这里插入图片描述
普通情况下,派生类内存的布局肯定先是基类再是派生类,基类指针指向派生类的时候,基类指针指向的就是派生类的起始地址。

但是,虚继承就不一样了。基类被虚继承以后,基类成为一个虚基类了,虚基类的数据要被搬到派生类的最后面,然后在原来的地方补一个vbptr。
在这里插入图片描述
现在,再用基类指针指向派生类对象的时候,永远指向的是派生类中基类部分的起始地址(vfptr)。
在这里插入图片描述
但是释放内存的时候出错了,因为派生类对象是从vbptr的地方开始开辟的,但是释放内存的时候,p持有的地址是虚基类的起始地址,
在这里插入图片描述
delete p就是从虚基类部分的起始地址开始delete的,这就是有问题了!

释放内存的时候出错了。

如果是用基类的指针指向栈上的或者全局的对象,就没有关系,因为比如在栈上出作用域,栈内存自动回收。

#include <iostream>
using namespace std;

class A
{
public:
	virtual void func() { cout << "call A::func" << endl; }
	void operator delete(void* ptr)
	{
		cout << "operator delete p:" << ptr << endl;
		free(ptr);
	}
private:
	int ma;
};
class B : virtual public A
{
public:
	void func() { cout << "call B::func" << endl; }

	void* operator new(size_t size)
	{
		void* p = malloc(size);
		cout << "operator new p:" << p << endl;
		return p;
	}
private:
	int mb;
};

int main()
{
	//基类指针指向派生类对象,永远指向的是派生类基类部分数据的起始地址
	A* p = new B(); //B::vftable
	cout << "main p:" << p << endl;
	p->func();
	delete p;

	return 0;
}

在这里插入图片描述
在这里插入图片描述

  • 对于编译器来说,在delete的时候,应该自己知道应该把这个内存从vbptr开始free。
  • 和编译器产生的指令有关。
  • 我们Windows的VS编译器都是有问题的,比较呆。
  • 对于linux的g++来说,在delete的时候自动偏移到new内存的起始部分开始free。

如果我们不考虑堆,弄成是栈上的对象:
在这里插入图片描述
在这里插入图片描述
不涉及内存的free,栈上的对象,出作用域自动释放。

疑问

有一疑问:派生类为什么不这样画?
在这里插入图片描述
vfptr不是派生类的吗?为什么要画到基类里面?(上图那种情况是派生类自己有虚函数,基类里面没有虚函数,vfptr就是归派生类自己的。)

但是如果派生类是从基类继承而来,而且基类有虚函数,那么vfptr相当于就是从基类继承下来的,因为基类里已经有一个虚函数指针了,派生类用1个vfptr就可以了,提供内存的利用率。(两个类合用一个虚函数指针)

在这里插入图片描述
在这里插入图片描述
现在vfptr是-8,表示和对象起始地址的偏移量!

  • 4
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
C++中的继承多态和虚函数是面向对象编程的重要概念。 继承是指一个类可以从另一个类继承属性和方法。子类可以继承父类的公有成员和保护成员,但不能继承私有成员。通过继承,子类可以重用父类的代码,并且可以添加自己的特定功能。继承可以实现代码的重用和层次化的设计。 多态是指同一个函数可以根据不同的对象调用不同的实现。多态可以通过虚函数来实现。虚函数是在基类中声明为虚拟的函数,它可以在派生类中被重写。当通过基类指针或引用调用虚函数时,实际调用的是派生类中的实现。这样可以实现动态绑定,即在运行时确定调用的函数。 虚函数的原理是通过虚函数表来实现的。每个包含虚函数的类都有一个虚函数表,其中存储了虚函数的地址。当调用虚函数时,编译器会根据对象的类型在虚函数表中查找对应的函数地址并调用。 综上所述,C++中的继承多态和虚函数是实现面向对象编程的重要机制,它们可以提高代码的灵活性和可扩展性。 #### 引用[.reference_title] - *1* *3* [C++多态之 虚函数和虚函数表](https://blog.csdn.net/weixin_46053588/article/details/121231465)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [c++多态及虚函数表内部原理实战详解](https://blog.csdn.net/bitcarmanlee/article/details/124830241)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

liufeng2023

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值