C++多态的实现机制

本文深入探讨了C++中多态的实现原理,通过对比指针和对象访问虚函数的过程,解释了为何多态必须通过指针或引用实现。文章详细分析了虚表和类对象的内存模型,揭示了基类指针指向派生类对象时如何正确调用派生类虚函数。
摘要由CSDN通过智能技术生成

首先问一个问题:为什么C++中一定要通过指针或者引用才能实现多态,通过对象就不行?

我们需要一些基础的知识。

一、 虚表

虚表的作用有两点:

1. 存放虚函数。

2. 使类的大小不会因为虚函数的个数的不同而差异化。因为类的对象中只维护一个指针,指向这个 virtual table。

二、类对象的内存模型

#include<iostream>
using namespace std;
class Point2d
{
public:
	Point2d(int x, int y) :m_x(x), m_y(y) {}
	virtual void Print()
	{
		cout << "(" << m_x << "," << m_y << ")" << endl;
	}
public:
	int m_x;
	int m_y;
};
class Point3d :public Point2d
{
public:
	Point3d(int x, int y, int z) :Point2d(x, y), m_z(z) {}
	virtual void Print()
	{
		cout << "(" << m_x << "," << m_y << "," << m_z << ")" << endl;
	}
public:
	int m_z;
};

对于这段代码,Point2d 和 Point3d 在内存中的模型如下:

Point2d 内存模型:

Point3d 内存模型:

三、 类成员相对类的偏移

在一个类中,所有成员的偏移位置都可以通过指向类成员的指针获取,比如要获取Point2d 类中的 m_x 成员的偏移位置,可以用如下代码:

int Point2d::* p = &Point2d::m_x;

有了以上的知识,我们就可以来解决最开始的问题了:为什么C++中一定要通过指针或者引用才能实现多态,通过对象就不行?

首先是通过类的对象来访问一个虚函数:

int main()
{
	Point3d p3(1, 2, 3);
	Point2d p2(4, 5);
	p2 = p3;
	p2.Print();
	return 0;
}

定义一个 Point3d 的对象,并且赋值给一个 Point2d 的对象,然后调用虚函数 Print ,输出的结果是:

很显然,并没有调用 Point3d 的虚函数,而是调用了 Point2d 的虚函数。

原因:

在将 Point3d 对象 p3 赋值给 Point2d 对象 p2 的过程中,发生了截断(想象一下将上面的对象模型的 Point3d 从开始部分覆盖到 Point2d 上,多余的部分将被截断,为什么? 因为派生类太大了放不下啊),也就是 p3 的派生类部分被丢弃,只有基类部分被保存在 p2 对象中,所以通过 p2 调用 Print 虚函数调用的就是 Point2d 的虚函数。

然后是通过指针或者引用来调用一个虚函数(由于指针和引用道理一样,所以只说明指针的情况)

int main()
{
	Point3d *p3 = new Point3d(1, 2, 3);
	Point2d *p2 = p3;
	p2->Print();
	return 0;
}

定义一个 Point3d 对象的指针,然后让一个 Point2d 类的指针指向这个 Point3d 对象的指针,然后调用 p2 的虚函数,输出的结果是调用了 p3 的虚函数:

这次虽然是通过基类指针来调用的虚函数,但是调用的确是子类的虚函数。

原因:

通过指针或者引用进行虚函数的访问,由于指针或者引用的对象并不知道(可能是基类,也可能是派生类,因为基类指针可以指向派生类),所以这个指针或者引用内部的成员的地址需要延迟到运行期才能确定下来,指针所指向的对象的类型确定后,如果这个指针或者引用指向的是基类,那么调用的虚函数的地址就从基类的首地址加上基类中这个虚函数的偏移位置,如果这个指针或者引用指向派生类,那么调用的虚函数的地址就是派生类的首地址加上派生类中这个虚函数的偏移位置,因此就导致了多态的结果(也就是基类指针和派生类指针调用同一个虚函数,产生的结果不同)。

 

 

如有错误,欢迎指正。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值