C++多态及虚表深度剖析

文章内容纲要:

(一):对象类型
(二):多态
(三):虚表剖析
(四):带有虚函数多继承


一、对象类型

二、多态


静态多态
编译器在编译期间完成的,编译器根据函数实参的类型(可能会进行隐式类型转换),可推 断出要调用那个函
数,如果有对应的函数就调用该函数,否则出现编译错误,例如:



【动态多态】动态绑定:在程序执行期间(非编译期)判断所引用对象的实际类型,根据其实际类型调用相应的方法。使用virtual关键字修饰类的成员函数时,指明该函数为虚函数,派生类需要重新实现,编译器将实现动态绑定。

三、虚表剖析

1:简单类虚表剖析

测试代码如下:

#include <iostream>
using namespace std;

class Base
{
public:
	 void Funtest0()
	{
		cout << "Base::Funtest0" << endl;
	}
	virtual void Funtest1()
	{
		cout << "Base::Funtest1" << endl;
	}
	virtual void Funtest2()
	{
		cout << "Base::Funtest2" << endl;
	}
public:
	int g_val;
};

typedef void(*pFun)();

int main()
{
	cout << sizeof(Base) << endl;
	Base b1;
	b1.g_val = 10;
	pFun* p1 = (pFun*)(*(int*)&b1);
	while (*p1)
	{
		(*p1)();
		p1++;
	}
	return 0;
}


首先创建一个简单类,有三个成员函数,其中另个为虚函数,一个为正常函数,另外包括一个数据成员g_val,当
我们查看其内存存储模型的时候会发现 这个类占有 8 个字节

而且前四个字节是一个很大的数据, 而后四个字节表示了数据成员 g_val,那么这个前四个字节是什么?是一个数据
成员 ?还是别的东西的?
咋一看其实第一反应有点像是一个地址,不妨我们按照地址的形式将其打开,特别提醒:注意自身所用编译器使
用的大小端存储方式(我所用的编程软件为 小端存储)
大端存储:
小端存储:


打开后,发现了某些特点,其中貌似还是有两个地址,然后以00 00 00 00结尾,我们不妨假设以上的内容均为函数的地址,则 尝试用某种方法按照地址去访问该函数,学习过 c 的同学都知道,我们访问函数的实质是按照函数指针来
调用函数,则在此可按照猜想得到结果:

查看反汇编会发现,实际上在类的构造函数中 将一个地址填写在类对象的前四个字节,这就是虚表,虚表中保留了类
虚函数的地址。



2:继承关系中的虚表剖析
①:无覆盖
#include <iostream>
using namespace std;

class Base
{
public:
		virtual void FunTest1()
		{ 
			cout << "CBase::FunTest1()" << endl;
		}
		void FunTest2()
		{ 
			cout << "CBase::FunTest2()" << endl;
		}
		virtual void FunTest3()
		{
			cout << "CBase::FunTest3()" << endl;
		}
		virtual void FunTest4()
		{ 
			cout << "CBase::FunTest4()" << endl;
		}
public:

		int b_val;
};


class Derive :public Base
{
public:

	int d_val;
};

typedef void(*pFun)();

int main()
{
	Base b1;
	b1.b_val = 10;
	cout << "this is Base" << endl;
	cout << sizeof(Base) << endl;
	pFun* p1 = (pFun*)(*(int*)&b1);
	while (*p1)
	{
		(*p1)();
		p1++;
	}

	cout << "this is Derive" << endl;
	cout << sizeof(Derive) << endl;
	Derive d1;
	d1.b_val = 12;
	d1.d_val = 13;
	p1 = (pFun*)(*(int*)&d1);
	while (*p1)
	{
		(*p1)();
		p1++;
	}
	return 0;
}
对于这种公有继承关系,我们依旧查看其内存存储模型可以得到

基类:


派生类:


两者之间的输出内容是一模一样的,只是在存储模型上派生类多了派生类自己的数据成员。根据上面讲的,既然
基类中有虚函数,那么继承后的派生类应该也有自己的虚表,但是实际上 只有一张虚表

猜想:派生类的虚表和基类的虚表是不是同一张呢?如果不是一张虚表,那么为什么在内存中只有一张虚表?
详见 ②有覆盖!!!!

②:有覆盖
#include <iostream>
using namespace std;

class Base
{
public:
		virtual void FunTest1()
		{ 
			cout << "CBase::FunTest1()" << endl;
		}

		virtual void FunTest2()
		{ 
			cout << "CBase::FunTest2()" << endl;
		}

		virtual void FunTest3()
		{
			cout << "CBase::FunTest3()" << endl;
		}
		virtual void FunTest4()
		{ 
			cout << "CBase::FunTest4()" << endl;
		}
public:

		int b_val;
};


class Derive :public Base
{
public:
	void FunTest1()
	{
		cout<< "CDerived::FunTest1()" << endl; 
	}

	virtual void FunTest2()
	{
		cout << "CDerived::FunTest2()" << endl;
	}

	virtual void FunTest3()
	{
		cout << "CDerived::FunTest3()" << endl;
	}

	virtual void FunTest5()
	{
		cout << "CDerived::FunTest5()" << endl;
	}
public:

	int d_val;
};

typedef void(*pFun)();

int main()
{
	Base b1;
	b1.b_val = 10;
	cout << "this is Base" << endl;
	cout << sizeof(Base) << endl;
	pFun* p1 = (pFun*)(*(int*)&b1);
	while (*p1)
	{
		(*p1)();
		p1++;
	}

	cout << "this is Derive" << endl;
	cout << sizeof(Derive) << endl;
	Derive d1;
	d1.b_val = 12;
	d1.d_val = 13;
	p1 = (pFun*)(*(int*)&d1);
	while (*p1)
	{
		(*p1)();
		p1++;
	}
	return 0;
}

基类:

派生类:



有覆盖 实际上就是成员函数中发生了重写(覆盖)

重写(覆盖):
在继承关系中,当基类中含有虚函数,而派生类继承与基类(不同作用域),在派生类中出现了与基类某函数的函数名相同、函数返回值相同(协变除外)、形参列表相同,那么就构成了重写; 其作用如名一样,重写的函数替换虚表中基类对应虚函数的位置被对象调用


注意:仔细查看基类和派生类两张图中CBase::FunTest4() 函数的地址,和发生重写函数的地址,我们发现前者是一样的,后者不同,验证了重写的理论

探究:派生类在构造函数形参列表时创建基类对象的虚表和完成自身创建后的虚表是否是同一虚表?

实际上派生类继承基类,在创建派生类对象的时候的顺序:


那么在派生类的构造函数初始化列表调用了基类的构造函数,基类则在前四个字节创建自己的虚表,然后在基类构造结束后,派生类接着在初始化列表中构造自己的空间及成员,也创建一张虚表将基类虚表中的虚函数地址复制一份存在自己的虚表中并用派生类的虚表地址覆盖前四个字节,如果有重写虚函数的情况,则按照重写的概念替换。

四、带有虚函数多继承&菱形继承对象模型剖析

1:带有虚函数多继承
①:派生类有自己特有的虚函数

#include <iostream>
using namespace std;
class Base1
{
public:
	virtual void Funtest1()
	{
		cout << "Base1::Funtest1" << endl;
	}
	virtual void Funtest2()
	{
		cout << "Base1::Funtest2" << endl;
	}
public:
	int base1;
};

class Base2
{
public:
	virtual void Funtest3()
	{
		cout << "Base1::Funtest3" << endl;
	}
	virtual void Funtest4()
	{
		cout << "Base1::Funtest4" << endl;
	}

public:
	int base2;
};

class Derive:public Base1, public Base2
{
public:
	void Funtest1()
	{
		cout << "Derive::Funtest1" << endl;
	}
	virtual void Funtest4()
	{
		cout << "Derive::Funtest4" << endl;
	}

	virtual void Funtest5()
	{
		cout << "Derive::Funtest5" << endl;
	}

public:
	int derive;
};

typedef void(*pFun)();

int main()
{
	cout << sizeof(Derive) << endl;
	Derive d1;
	d1.base1 = 10;
	d1.base2 = 11;
	d1.derive = 12;
	int i = 0;

	for (; i < 3;i+=2)
	{
		pFun* p1 = (pFun*)(*((int*)&d1+i));
		while (*p1)
		{
			(*p1)();
			p1++;
		}
	}
	return 0;
}

其对象模型是:


实质上若派生类有特有的虚函数,那么将该虚函数的地址存放在多继承中第一个声明继承的基类的虚函数最后

②:派生类没有特定的虚函数

大致与①相同,如果发生重写,则按照重写的概念替换

2:菱形继承
#include <iostream>
using namespace std;

class Top
{
public:

	virtual void Funtest0()
	{
		cout << "Top::Funtest0()" << endl;
	}

	virtual void Funtest1()
	{
		cout << "Top::Funtest1()" <<endl;
	}

	virtual void Funtest2()
	{
		cout << "Top::Funtest2()" << endl;
	}
	virtual void Funtest3()
	{
		cout << "Top::Funtest3()" << endl;
	}

	int top;
};

class Left:public Top
{
public:
	virtual void Funtest1()
	{
		cout << "Left::Funtest1()" << endl;
	}
	virtual void Funtest4()
	{
		cout << "Left::Funtest4()" << endl;
	}

	int left;
	
};

class Right:public Top
{
public:
	virtual void Funtest2()
	{
		cout << "Right::Funtest2()" << endl;
	}
	virtual void Funtest5()
	{
		cout << "Right::Funtest5()" << endl;
	}

	int right;
};

class End :public Left, public Right
{
public:
	virtual void Funtest0()
	{
		cout << "End::Funtest0()" << endl;
	}
	virtual void Funtest4()
	{
		cout << "End::Funtest4()" << endl;
	}

	virtual void Funtest5()
	{
		cout << "End::Funtest5()" << endl;
	}

	virtual void Funtest6()
	{
		cout << "End::Funtest6()" << endl;
	}

	int end;
};

typedef void(*pFun)();
void Print()
{
	End d;
	Left& l = d;
	pFun* p1 = (pFun*)(*(int*)&l);
	while (*p1)
	{
		(*p1)();
		p1++;
	}

	cout << "#################" << endl;
	Right& g = d;
	p1 = (pFun*)(*(int*)&g);
	while (*p1)
	{
		(*p1)();
		p1++;
	}
}
int main()
{
	Print();
	cout << sizeof(End) << endl;
	End d1;
	d1.left = 10;
	d1.Left::top = 11;
	d1.right = 12;
	d1.Right::top = 13;
	d1.end = 14;

	return 0;
}

其对象模型为



其实质是 按照普通多继承形式将派生类独有的虚函数放在了首位置4个字节最末端












  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值