C++ 多态的实现原理:虚表与晚绑定

C++ 多态

C++中多态是怎么使用的?

用基类指针或引用 存储/绑定 派生类 对象地址/对象,如果访问的接口是虚函数,则访问的是虚表中对应函数,而不只是基类函数。
可以看出,这种操作能够实现一个接口多种实现,所以我们将它称为多态。

当给类写了虚函数,类就会增加一个虚指针(虚函数表指针)成员,其地址为类的首地址。并且编译器会生成一个虚函数表,用于存储类的所有虚函数的指针。
如图:
在这里插入图片描述
代码测试:测试是否为上图结构

#include <iostream>
using namespace std;
class Base
{
	public:
	virtual void fun(){
		cout << "function from Base" << endl;
	}
	void test()
	{
		void (*f)() = (void(*)())(*(int*)(*(int*)this));
		f();
	}
};
int main(){
	Base b;
	b.test();
} 

运行结果:
在这里插入图片描述

多态的实现原理:

早期绑定的概念:
C++编译器在编译的时候,要确定每个对象调用非虚函数的地址,并且将它绑定下来。而虚函数是在程序运行时通过虚指针访问虚表再调用对应的虚函数。

我们以以下例子分析虚函数与普通函数的不同:

//测试代码:
#include <iostream>
using namespace std;
class Base
{
	public:
	virtual void v_fun(){
		cout << "virtual function from Base" << endl;
	}
	void fun(){
		cout << "function from Base" << endl;
	}
};
class Derived
	:public Base
{
	public:
	virtual void v_fun(){
		cout << "virtual function from Derived" << endl;
	}
	void fun(){
		cout << "function from Derived" << endl;
	}
};
int main(){
	Derived d;
	Base* bptr = &d;
	bptr->v_fun();
 	bptr->fun();
} 

运行结果:
在这里插入图片描述

分析:
晚绑定:
当编译器编译到 bptr->v_fun()时,由于调用的是虚函数,编译器并不会绑定函数地址,而是程序运行到这个地方时,访问bptr所指向的虚指针再通过虚指针访问虚表中对应的函数。由于虚表是Derived对象的虚表,所以访问的是Derived对象的v_fun函数。
早绑定
当编译器编译到 bptr->fun()时,由于调用的是普通函数,所以编译器会直接将其绑定至对象对应函数地址,注意:bptr作为基类指针,不通过虚表则只能访问到基类的成员函数,所以此时绑定的是基类函数的地址。

虚表的继承:
当派生类继承一个含有虚表的基类时,派生类会先继承基类的虚表,然后改写虚表中函数指针,如果有自己增加的虚函数,则在虚表中添加。但是需要我们注意的是,基类指针只能访问基类已经实现的方法,所以就算派生类中添加了虚函数,通过基类指针也是访问不到的。那为什么还要添加到虚表呢?因为派生类也可以作为基类被继承。

发布了8 篇原创文章 · 获赞 22 · 访问量 3315

猜你喜欢

转载自blog.csdn.net/weixin_43406295/article/details/89606312