从C向C++9——typeid和RTTI

一.typeid运算符

1.语法

typeid 运算符用来获取一个表达式的类型信息。类型信息对于编程语言非常重要,它描述了数据的各种属性:

  • 对于基本类型(int、float 等C++内置类型)的数据,类型信息所包含的内容比较简单,主要是指数据的类型。
  • 对于类类型的数据(也就是对象),类型信息是指对象所属的类、所包含的成员、所在的继承关系等。

型信息是创建数据的模板,数据占用多大内存、能进行什么样的操作、该如何操作等,这些都由它的类型信息决定。

typeid 的操作对象既可以是表达式,也可以是数据类型,下面是它的两种使用方法:

typeid( dataType )
typeid( expression )

dataType 是数据类型,expression 是表达式,这和 sizeof 运算符非常类似,只不过 sizeof 有时候可以省略括号( ),而 typeid 必须带上括号。

typeid 会把获取到的类型信息保存到一个 type_info 类型的对象里面,并返回该对象的常引用;当需要具体的类型信息时,可以通过成员函数来提取。

2.使用

#include <iostream>
#include <typeinfo>
using namespace std;class Base{ };struct STU{ };int main(){//获取一个普通变量的类型信息int n = 100;const type_info &nInfo = typeid(n);cout<<nInfo.name()<<" | "<<nInfo.raw_name()<<" | "<<nInfo.hash_code()<<endl;//获取一个字面量的类型信息const type_info &dInfo = typeid(25.65);cout<<dInfo.name()<<" | "<<dInfo.raw_name()<<" | "<<dInfo.hash_code()<<endl;//获取一个对象的类型信息Base obj;const type_info &objInfo = typeid(obj);cout<<objInfo.name()<<" | "<<objInfo.raw_name()<<" | "<<objInfo.hash_code()<<endl;//获取一个类的类型信息const type_info &baseInfo = typeid(Base);cout<<baseInfo.name()<<" | "<<baseInfo.raw_name()<<" | "<<baseInfo.hash_code()<<endl;//获取一个结构体的类型信息const type_info &stuInfo = typeid(struct STU);cout<<stuInfo.name()<<" | "<<stuInfo.raw_name()<<" | "<<stuInfo.hash_code()<<endl;//获取一个普通类型的类型信息const type_info &charInfo = typeid(char);cout<<charInfo.name()<<" | "<<charInfo.raw_name()<<" | "<<charInfo.hash_code()<<endl;//获取一个表达式的类型信息const type_info &expInfo = typeid(20 * 45 / 4.5);cout<<expInfo.name()<<" | "<<expInfo.raw_name()<<" | "<<expInfo.hash_code()<<endl;return 0;
}

从本例可以看出,typeid 的使用非常灵活,它的操作数可以是普通变量、对象、内置类型(int、float等)、自定义类型(结构体和类),还可以是一个表达式。

本例中还用到了 type_info 类的几个成员函数,下面是对它们的介绍:

  • name() 用来返回类型的名称。
  • raw_name() 用来返回名字编码(Name Mangling)算法产生的新名称。
  • hash_code() 用来返回当前类型对应的 hash 值。hash 值是一个可以用来标志当前类型的整数,有点类似学生的学号、公民的身份证号、银行卡号等。不过 hash 值有赖于编译器的实现,在不同的编译器下可能会有不同的整数,但它们都能唯一地标识某个类型。

3.补充

​ 遗憾的是,C++ 标准只对 type_info 类做了很有限的规定,不仅成员函数少,功能弱,而且各个平台的实现不一致。例如上面代码中的 name() 函数,nInfo.name()objInfo.name()VC/VS 下的输出结果分别是intclass Base,而在 GCC下的输出结果分别是i4Base

C++ 标准规定,type_info 类至少要有如下所示的 4 个 public 属性的成员函数,其他的扩展函数编译器开发者可以自由发挥,不做限制。

    1. 原型:const char* name() const;

​ 返回一个能表示类型名称的字符串。但是C++标准并没有规定这个字符串是什么形式的,例如对于上面的objInfo.name()语句,VC/VS 下返回“class Base”,但 GCC 下返回“4Base”。

    1. 原型:bool before (const type_info& rhs) const;

​ 判断一个类型是否位于另一个类型的前面,rhs 参数是一个 type_info 对象的引用。但是C++标准并没有规定类型的排列顺序,不同的编译器有不同的排列规则,程序员也可以自定义。要特别注意的是,这个排列顺序和继承顺序没有关系,基类并不一定位于派生类的前面。

    1. 原型:bool operator== (const type_info& rhs) const;

​ 重载运算符“==”,判断两个类型是否相同,rhs 参数是一个 type_info 对象的引用。

    1. 原型:bool operator!= (const type_info& rhs) const;

​ 重载运算符“!=”,判断两个类型是否不同,rhs 参数是一个 type_info 对象的引用。

raw_name() 是 VC/VS 独有的一个成员函数,hash_code() 在 VC/VS 和较新的 GCC 下有效。

4.声明

最后我们再来看一下 type_info 类的声明,以进一步了解它所包含的成员函数以及这些函数的访问权限。type_info 类位于typeinfo头文件,声明形式类似于:

class type_info {
public:virtual ~type_info();int operator==(const type_info& rhs) const;int operator!=(const type_info& rhs) const;int before(const type_info& rhs) const;const char* name() const;const char* raw_name() const;
private:void *_m_data;char _m_d_name[1];type_info(const type_info& rhs);type_info& operator=(const type_info& rhs);
};

它的构造函数是 private 属性的,所以不能在代码中直接实例化,只能由编译器在内部实例化(借助友元)。而且还重载了“=”运算符,也是 private 属性的,所以也不能赋值。

二.多态补充

1.RTTI

根据前面讲过的知识,C++ 的对象内存模型主要包含了以下几个方面的内容:

  • 如果没有虚函数也没有虚继承,那么对象内存模型中只有成员变量。
  • 如果类包含了虚函数,那么会额外添加一个虚函数表,并在对象内存中插入一个指针,指向这个虚函数表。
  • 如果类包含了虚继承,那么会额外添加一个虚基类表,并在对象内存中插入一个指针,指向这个虚基类表。

一般情况下,在编译期间就能确定一个表达式的类型,但是当存在多态时,有些表达式的类型在编译期间就无法确定了,必须等到程序运行后根据实际的环境来确定。

#include <iostream>
using namespace std;
//基类
class Base{
public:virtual void func();
protected:int m_a;int m_b;
};
void Base::func(){ cout<<"Base"<<endl; }
//派生类
class Derived: public Base{
public:void func();
private:int m_c;
};
void Derived::func(){ cout<<"Derived"<<endl; }
int main(){Base *p;int n;cin>>n;if(n <= 100){p = new Base();}else{p = new Derived();}cout<<typeid(*p).name()<<endl;return 0;
}

基类 Base 包含了一个虚函数,派生类 Derived 又定义了一个原型相同的函数遮蔽了它,这就构成了多态。p 是基类的指针,可以指向基类对象,也可以指向派生类对象;*p表示 p 指向的对象。

从代码中可以看出,用户输入的数字不同,*p表示的对象就不同,typeid 获取到的类型也就不同,编译器在编译期间无法预估用户的输入,所以无法确定*p的类型,必须等到程序真的运行了、用户输入完毕了才能确定*p的类型。

如果类包含了虚函数,那么该类的对象内存中还会额外增加类型信息,也即 type_info 对象。以上面的代码为例,Base 和 Derived 的对象内存模型如下图所示:

![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img-home.csdnimg.cn/images/20230724024159.png?origin_url=…%2F…%2FMyBlog%2Fmy-blog%2F.vuepress%2Fpublic%2Fimage-20240114115000351.png&pos_id=img-ZcFn26YJ-1709431738660在这里插入图片描述

编译器会在虚函数表 vftable 的开头插入一个指针,指向当前类对应的 type_info 对象。当程序在运行阶段获取类型信息时,可以通过对象指针 p 找到虚函数表指针 vfptr,再通过 vfptr 找到 type_info 对象的指针,进而取得类型信息。

编译器在编译阶段无法确定 p 指向哪个对象,也就无法获取*p的类型信息,但是编译器可以在编译阶段做好各种准备,这样程序在运行后可以借助这些准备好的数据来获取类型信息。这些准备包括:

  • 创建 type_info 对象,并在 vftable 的开头插入一个指针,指向 type_info 对象。
  • 将获取类型信息的操作转换成类似**(p->vfptr - 1)这样的语句。

这种在程序运行后确定对象的类型信息的机制称为运行时类型识别(Run-Time Type Identification,RTTI)。在 C++ 中,只有类中包含了虚函数时才会启用 RTTI 机制,其他所有情况都可以在编译阶段确定类型信息。

2.函数绑定

C/C++ 用变量来存储数据,用函数来定义一段可以重复使用的代码,它们最终都要放到内存中才能供 CPU 使用。CPU 通过地址来取得内存中的代码和数据,程序在执行过程中会告知 CPU 要执行的代码以及要读写的数据的地址。

CPU 访问内存时需要的是地址,而不是变量名和函数名!变量名和函数名只是地址的一种助记符,当源文件被编译和链接成可执行程序后,它们都会被替换成地址。编译和链接过程的一项重要任务就是找到这些名称所对应的地址。

假设变量 a、b、c 在内存中的地址分别是 0X1000、0X2000、0X3000,那么加法运算c = a + b;将会被转换成类似下面的形式:

0X3000 = (0X1000) + (0X2000);

( )表示取值操作,整个表达式的意思是,取出地址 0X1000 和 0X2000 上的值,将它们相加,把相加的结果赋值给地址为 0X3000 的内存。

变量名和函数名为我们提供了方便,让我们在编写代码的过程中可以使用易于阅读和理解的英文字符串,不用直接面对二进制地址,那场景简直让人崩溃。

我们不妨将变量名和函数名统称为符号(Symbol),找到符号对应的地址的过程叫做符号绑定。本节只讨论函数名和地址的绑定,变量名也是类似的道理。

函数体是内存中的一个代码段,函数名就表示该代码段的首地址,函数执行时就从这里开始。说得简单一点,就是必须要知道函数的入口地址,才能成功调用函数。找到函数名对应的地址,然后将函数调用处用该地址替换,这称为函数绑定

一般情况下,在编译期间(包括链接期间)就能找到函数名对应的地址,完成函数的绑定,程序运行后直接使用这个地址即可。这称为静态绑定

但是有时候在编译期间想尽所有办法都不能确定使用哪个函数,必须要等到程序运行后根据具体的环境或者用户操作才能决定。这称为动态绑定

动态绑定的本质:编译器在编译期间不能确定指针指向哪个对象,只能等到程序运行后根据具体的情况再决定。

能找到函数名对应的地址,完成函数的绑定,程序运行后直接使用这个地址即可。这称为静态绑定

但是有时候在编译期间想尽所有办法都不能确定使用哪个函数,必须要等到程序运行后根据具体的环境或者用户操作才能决定。这称为动态绑定

动态绑定的本质:编译器在编译期间不能确定指针指向哪个对象,只能等到程序运行后根据具体的情况再决定。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/506425.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

trivy扫描出了mvn dependency:tree中不存在的jar

Dependency appears in Trivy but not when running maven tree. #2287 Dependency appears in Trivy but not when running maven tree. Issue #2287 aquasecurity/trivy GitHub 方法&#xff1a; step 1: 用这个命令&#xff0c;可以打印出详细的路径 trivy -d rootfs…

MySQL数据库运维第一篇(日志与主从复制)

文章目录 一、错误日志二、二进制日志三、查询日志四、慢查询日志&#xff08;记录超时的sql语句&#xff09;五、主从复制概括六、主从复制原理七、搭建主从复制八、主从复制的测试 在这篇深入的技术文章中&#xff0c;作者将以明晰透彻的方式详细介绍MySQL数据库中关键的日志…

CSM是什么意思?

CSM(Customer Service Management)是企业客户服务管理的信息化&#xff08;IT&#xff09;解决方案架构。本着以客户为中心的管理理念&#xff0c;搭建企业客户服务管理平台&#xff0c;实现企业以客户为中心的管理时代的竞争战略。 CSM的核心是以客户为中心&#xff0c;实现对…

Vins-Moon配准运行

Vins-Moon运行 源码地址电脑配置环境配置编译适配Kitti数据集运行结果Euroc数据集kitti数据集 evo评估&#xff08;KITTI数据&#xff09;输出轨迹(tum格式)结果 源码地址 源码链接&#xff1a;https://github.com/HKUST-Aerial-Robotics/VINS-Mono.git 电脑配置 Ubuntu 18.…

新闻采访与写作的技巧

传媒如春雨&#xff0c;润物细无声&#xff0c;大家好&#xff0c;我是51媒体网胡老师。 新闻采访与写作的技巧是新闻工作者必须掌握的重要技能。以下是一些关于新闻采访与写作的技巧&#xff1a; 新闻采访与写作的技巧 一、新闻采访技巧 充分准备&#xff1a;在采访前要对被…

ssm637教材管理系统

** &#x1f345;点赞收藏关注 → 私信领取本源代码、数据库&#x1f345; 本人在Java毕业设计领域有多年的经验&#xff0c;陆续会更新更多优质的Java实战项目希望你能有所收获&#xff0c;少走一些弯路。&#x1f345;关注我不迷路&#x1f345;** 一 、设计说明 1.1研究背…

spring项目单元测试

单元测试 每个功能开发完&#xff0c;项目上线前都要做单元测试 引入依赖 AssertJ&#xff1a;断言 测试用例 编写测试用例要求&#xff1a;保证测试方法的独立性 当前测试方法不能依赖于别的测试方法&#xff0c;也不依赖于数据库中某条特定数据。 如A方法插入一条数据&a…

鸿蒙开发的PersistentStorage和用户首选项(Preferences)区别、应用场景详解

项目场景: 鸿蒙(HarmonyOS)是华为公司推出的一种分布式操作系统。在鸿蒙开发中,PersistentStorage(持久化存储)和用户首选项(Preferences)是两种不同的数据存储方式,它们在应用场景上有一些明显的区别。 问题描述 问题: 现在有点搞不清保存什么信息用 PersistentS…

OJ:链表的中间结点

876. 链表的中间结点 - 力扣&#xff08;LeetCode&#xff09; 思路 思路&#xff1a;首先最容易想到的思路是什么呢&#xff0c;就是先遍历一遍链表&#xff0c;用一个值count来记录链表的长度&#xff0c;然后我们运用除法&#xff0c;/2&#xff0c;结果是几&#xff0c;就…

C/C++工程师面试题(数据库篇)

索引的优缺点 索引是一种支持快速查找特定行的数据结构&#xff0c;如果没有索引&#xff0c;就需要遍历整个表进行查找。用于提高数据检索的速度和效率。 好处&#xff1a; 提高检索速度&#xff1a; 索引可以加快数据的检索速度&#xff0c;因为它们允许数据库系统直接定位到…

一个网站是怎么开发出来并上线让你可以访问到全流程介绍

一个网站是怎么开发出来并上线让你可以访问到全流程介绍 之前和公司一些前端开发的朋友聊天&#xff0c;问他们&#xff1a;你们平时是怎么发布包的&#xff0c;答曰&#xff1a;不知道&#xff01; 我很震惊&#xff0c;又问了身边一些做开发的同事朋友&#xff0c;居然很多人…

USLE模型-C因子的计算

首先得到FVC&#xff0c;并用掩膜提取研究区 在栅格计算器中&#xff0c;按下面公式计算C&#xff1a; Con(“qy”0,1,Con(“qy”>0.783,1,0.6509-0.3436 * Log10(100 * “qy”))) 最后导出为TIFF保存