首页 > 编程语言 > C++类模型漫谈(三)

C++类模型漫谈(三)

时间:2022-10-20 19:15:23浏览次数:45  
标签:虚表 函数 Method1 模型 漫谈 TypeA C++ TypeB ptr

系统基于32位,MSVC编译器,VS开发工具

1、上篇直接通过类型对象调用成员函数,这种方式无法实现多态。所谓多态意思对函数的调用呈现出不同的形态。

下面这个例子中 a_ptr为指向a_obj的指针,当调用虚函数时,不再像之前一样直接调用了,而是先从虚函数表获取函数地址间接调用。

而如果是调用非虚函数,没差别,不管是通过函数指针还是对象的方式调用,都是汇编代码直接生成了函数地址调用,效率高。

我们看到拥有虚函数的a_obj对象多出了一个__vfptr指针变量(放在对象首地址处),指向存放函数指针的虚函数表。32位环境下,指针大小通常为4个字节,所以这个时候输出sizeof(a_obj)为12个字节

class TypeA {
public:
	char a1 = 10;
	int a2 = 20;
	void virtual TypeA_Method1() {
		cout << "TypeA::TypeA_Method1" << endl;
	}
	void  TypeA_Method2() {
		cout << "TypeA::TypeA_Method2" << endl;
	}
};
int main()
{   
	TypeA a_obj;
	TypeA* a_ptr = &a_obj;
	a_ptr->TypeA_Method1();//通过虚函数表间接调用虚函数
	a_ptr->TypeA_Method2();//直接调用非函数
	cout << sizeof(a_obj) << endl;//输出12
	return 1;
}

2、类的单继承模式下,多态的实现。TypeB继承了TypeA,  b_obj中自然也包含了TypeA子对象,按照先TypeA子对象,再TypeB自己定义的那部分b1、b2。

因为两个类都定义了虚函数,所以无脑的实现方式是,有两个__vfptr,分别指向对应的虚函数表。

TypeB重写了TypeA的TypeA_Method1,所以对应的TypeA子对象的的虚表中函数指针为&TypeB::TypeA_Method1,TypeB自己部分的虚表中存了一个&TypeB::TypeB_Method1。

不过编译器发现可以把两个虚表合在一起,所以实际情况只会出现一个虚表,一个__vfptr。

class TypeA {
public:
	char a1 = 10;
	int a2 = 20;
	void virtual TypeA_Method1() {
		cout << "TypeA::TypeA_Method1" << endl;
	}
	void virtual TypeA_Method2() {
		cout << "TypeA::TypeA_Method2" << endl;
	}
};
class TypeB :public TypeA {
public:
	char b1 = 30;
	int b2 = 40;
	void virtual TypeA_Method1() {   //重写TypeA的虚函数
		cout << "TypeB::TypeA_Method" << endl;
	}
	void virtual TypeB_Method1() {
		cout << "TypeB::TypeB_Method" << endl;
	}
};
int main()
{   
	TypeB b_obj;
	TypeA* a_ptr = &b_obj;
	TypeB* b_ptr = &b_obj;

	a_ptr->TypeA_Method1();//TypeA类型指针调用重写的TypeA_Method1虚函数
	b_ptr->TypeA_Method1();//TypeB类型指针调用重写的TypeA_Method1虚函数
	b_ptr->TypeA_Method2();//TypeB类型指针调用继承下来的TypeA_Method2虚函数
	b_ptr->TypeB_Method1();//TypeB类型指针调用自己的虚函数TypeB_Method1

	return 1;
}

原来TypeA中的TypeA_Method1函数,被TypeB类中重写了,这个时候虚表存的是&TypeB::TypeA_Method1

a_ptr->TypeA_Method1()   查找__vfptr中虚表中的TypeA_Method1,得到&TypeB::TypeA_Method1

b_ptr->TypeA_Method1()   查找__vfptr中虚表中的TypeA_Method1,得到&TypeB::TypeA_Method1

b_ptr->TypeA_Method2()   查找__vfptr中虚表中的TypeA_Method2,得到&TypeA::TypeA_Method2

b_ptr->TypeB_Method1()   查找__vfptr中虚表中的TypeB_Method1,得到&TypeB::TypeB_Method1

如果a_ptr->TypeB_Method1()呢?

虽然此时看到好像虚表中也包含了TypeB_Method1,  但是该函数是属于TypeB类型中定义的,我们可以看到合并之前

并不存在于TypeA那部分的虚表中,所以这个时候调用是提示不存在的。

 

 

 

3、上面的例子是两个虚表进行了合并,现在设计一个合并不了的,让TypeC继承TypeA和TypeB。

c_obj中包含了TypeA子对象和TypeB子对象,按照顺序排下来,最后包含了TypeC类自己定义的c1、c2。

因为这三个类,都有自己的虚函数,所以正常无脑的做法,可以给每个类都安插一个__vfptr, 指向对应的虚表。

同样道理TypeA子对象部分其实就是c_obj的起始地址,所以把这两部分的虚函数合并一起,而TypeB子对象部分无法合并,单独一个虚表

所以实际c_obj内存中会出现两个__vfptr 对应两张虚表。

总结:不管是完整对象、或者是子对象,都会尝试把各自的__vfptr放到各自内存区域的起始地址处。因为TypeA子对象和TypeC对象起始地址相同,而__vfptr都想放在各自的起始位置处,所以直接合并用一个__vfptr,两个子虚表也合并一起了

可以想像成两个__vfptr上下重叠一起,对应的虚表也是重叠在一起。而TypeB子对象__vfptr也放在了起始位置,即&c_obj的值+12位置处。

class TypeA {
public:
	char a1 = 10;
	int a2 = 20;
	void virtual TypeA_Method1() {
		cout << "TypeA::TypeA_Method1" << endl;
	}
	void virtual TypeA_Method2() {
		cout << "TypeA::TypeA_Method2" << endl;
	}
};
class TypeB {
public:
	char b1 = 30;
	int b2 = 40;	
	void virtual TypeB_Method1() {
		cout << "TypeB::TypeB_Method1" << endl;
	}
};
class TypeC :public TypeA, public TypeB {
public:
	char c1 = 50;
	int c2 = 60;
	void virtual TypeA_Method1() {//重写了TypeA中的TypeA_Method1
		cout << "TypeC::TypeA_Method1" << endl;
	}
	void virtual TypeB_Method1() {//重写了TypeB中的TypeB_Method1
		cout << "TypeC::TypeB_Method1" << endl;
	}
	void virtual TypeC_Method1() {//TypeC自己的函数TypeC_Method1
		cout << "TypeC::TypeC_Method1" << endl;
	}
};
int main()
{   
	TypeC c_obj;
	TypeB* b_ptr = &c_obj;
	TypeC* c_ptr = &c_obj;
	b_ptr->TypeB_Method1();//b_ptr指针调用TypeB类型中定义的函数TypeB_Method1
	c_ptr->TypeB_Method1();//c_ptr指针调用TypeB类型中定义的函数TypeB_Method1

	return 1;
}

TypeB*  b_ptr=&c_obj  

c_obj的起始地址为0,而TypeB子对象部分的起始地址为12,所以这句代码的结果是编译器直接通过c_ptr+12的方式,得到的地址作为b_ptr指针的值。b_ptr指向了地址12开始的12-23空间范围的Type子对象。

b_ptr->TypeB_Method()  

编译器检测到TypeB_Method函数是TypeB类型中定义的,而这个时候b_ptr也是指向TypeB子对象,所以直接从12地址的__vfptr获取虚表地址,再从对应虚表中查到&TypeC::TypeB_Method1函数指针调用。这里是TypeC重写后的函数,调用该函数this需要c_obj对象的指针,但此时的b_ptr是指向TypeB子对象,因此需调调整。编译器有时候会生成一个thunk函数,函数里面先执行this+=12,用以调整this指针,再跳转到&TypeC::TypeB_Method1执行。因此这种情况下虚表里存的是&thunk指针。

c_ptr->TypeB_Method()   

编译器检测到TypeB_Method函数是TypeB类型中定义的,而此时c_ptr是指向TypeC对象,所以只能通过c_ptr+12的方式定位到TypeB子对象处,从定位到的地址拿到__vfptr,再找到对应的虚函数&TypeC::TypeB_Method1指针调用,传入c_ptr当做this指针。

 

 

标签:虚表,函数,Method1,模型,漫谈,TypeA,C++,TypeB,ptr
From: https://www.cnblogs.com/fuyun2000/p/16806456.html

相关文章

  • TCP/IP四层网络模型
    1.TCP/IP协议是什么?TCP/IP协议时一种网络体系模型的代名词,指的是多种协议的协议簇,即包含TCP、IP、MAC、UDP、HTTP、FTP等多种协议,它是四层网络模型,包含应用层、传输......
  • 双亲委派模型
    双亲委派目的好处:主要是为了安全性,避免用户自己写的类动态替换了Java的核心类,比如自己写了String替换了Java的String同时避免了类的重复加载,JVM对于类的区分,不仅仅根据......
  • 实验3 数组、指针与现代C++标准库
    实验5#pragmaonce#include<iostream>#include<string>#include<iomanip>usingnamespacestd;classinfo{public:info(stringnickname0,stringcontact......
  • 项目开发神器VsCode配置指南!(含C++、Python、Java环境配置)
    作者:吴忠强,东北大学,Datawhale成员本篇文章虽然是VsCode挂名,但其实介绍了两款神器:Vscode和Vim,这两个结合起来,开发效率蹭蹭蹭!!!之前接触过VsCode但很少用。总感觉写Python......
  • 作者解读ICML接收论文:如何使用不止一个数据集训练神经网络模型?
    作者:欧明锋,浙江大学导读:在实际的深度学习项目中,难免遇到多个相似数据集,这时一次仅用单个数据集训练模型,难免造成局限。是否存在利用多个数据集训练的可能性?本文带来解读。01......
  • 使用YOLOv5模型进行目标检测!
    作者:陈信达,华北电力大学,Datawhale成员目标检测是计算机视觉领域的一大任务,大致分为一阶段目标检测与两阶段目标检测。其中一阶段目标检测模型以YOLO系列为代表。最新的YOLOv......
  • 经典分类:线性判别分析模型!
    作者:小雨姑娘,康涅狄格大学,Datawhale成员这几天看了看SVM的推导,看的是真的头疼,那就先梳理基础的线性判别分析模型,加深对SVM的理解。线性判别分析是一种线性的分类模型。线性......
  • #打卡不停更#三方库移植之NAPI开发[2]C/C++与JS的数据类型转换
    在《三方库移植之NAPI开发[1]—HelloOpenHarmonyNAPI》通过一个HelloOpenHarmonyNAPI样例讲述了NPAI接口开发基础知识。本文在其基础上修改hellonapi.cpp文件,介绍JS类型......
  • C++ 值,指针,引用的讨论
    源自stackoverflow论坛,很有意义第一个问题,引用传递和按值传递的场合Therearefourmaincaseswhereyoushouldusepass-by-referenceoverpass-by-value:Ifyou......
  • JUC - 共享模型之工具 - 第六篇
    六、共享模型之工具1.线程池1.1自定义线程池步骤1:自定义拒绝策略接口@FunctionalInterface//拒绝策略interfaceRejectPolicy<T>{voidreject(BlockingQueu......