首页 > 其他分享 >【Cpp】RTTI 机制原理解析

【Cpp】RTTI 机制原理解析

时间:2023-10-07 14:25:35浏览次数:40  
标签:info 解析 const 类型 Cpp RTTI type 指针

References

什么是RTTI机制?

RTTI 是“Runtime Type Information”的缩写,意思是:运行时类型信息。它提供了运行时确定对象类型的方法。

RTTI 通过运行时类型信息程序能够使用基类的指针或引用来检查这些指针或引用所指的对象的实际派生类型。

听起来很像包含虚函数的多态机制,这里和虚函数又有哪些不同呢?

为什么需要 RTTI 机制

数组是十分常用的数据结构,而对经常使用C++的同学来说指针也是逃不开的拦路虎。相信在很多课程或书本上大家都看过说指针在内存中存储的是一个地址,而数组名也是一个地址。

和很多其他语言一样,C++是一种静态类型语言。其数据类型是在编译期就确定的,不能在运行时更改。然而由于面向对象程序设计中多态性的要求,C++中的指针或引用(Reference)本身的类型,可能与它实际代表(指向或引用)的类型并不一致。

有时我们需要将一个多态指针转换为其实际指向对象的类型,就需要知道运行时的类型信息,这就产生了运行时类型识别的要求。

在C++中存在虚函数,也就存在了多态性,对于多态性的对象,在程序编译时可能会出现无法确定对象的类型的情况。

当类中含有虚函数时,其基类的指针就可以指向任何派生类的对象,这时就有可能不知道基类指针到底指向的是哪个对象的情况,类型的确定要在运行时利用运行时类型标识做出。为了获得一个对象的类型可以使用 typeid 函数,该函数反回一个对type_info类对象的引用,要使用 typeid 必须使用头文件 typeinfo

C++中如何实现RTTI机制?

C++提供了两个关键字 typeiddynamic_cast和一个type_info类来支持RTTI:

  • dynamic_cast操作符:它允许在运行时刻进行类型转换,从而使程序能够在一个类层次结构安全地转换类型。

    dynamic_cast提供了两种转换方式,把基类指针转换成派生类指针,或者把指向基类的左值转换成派生类的引用。 

  • typeid操作符:它指出指针或引用指向的对象的实际派生类型。主要作用就是让用户知道当前的变量是什么类型的,比如以下代码:

    #include <iostream>
    #include <typeinfo>
    using namespace std;
    
    int main()
    {
         short s = 2;
         unsigned ui = 10;
         int i = 10;
         char ch = 'a';
         wchar_t wch = L'b';
         float f = 1.0f;
         double d = 2;
    
         cout<<typeid(s).name()<<endl; // short
         cout<<typeid(ui).name()<<endl; // unsigned int
         cout<<typeid(i).name()<<endl; // int
         cout<<typeid(ch).name()<<endl; // char
         cout<<typeid(wch).name()<<endl; // wchar_t
         cout<<typeid(f).name()<<endl; // float
         cout<<typeid(d).name()<<endl; // double
    
         return 0;
    }
    

对于C++支持的内建类型,typeid能完全支持,我们通过调用typeid函数,我们就能知道变量的信息。对于我们自定义的结构体,类呢?

#include <iostream>
#include <typeinfo>
using namespace std;

class A
{
public:
     void Print() { cout<<"This is class A."<<endl; }
};

class B : public A
{
public:
     void Print() { cout<<"This is class B."<<endl; }
};

struct C
{
     void Print() { cout<<"This is struct C."<<endl; }
};

int main()
{
     A *pA1 = new A();
     A a2;

     cout<<typeid(pA1).name()<<endl; // class A *
     cout<<typeid(a2).name()<<endl; // class A

     B *pB1 = new B();
     cout<<typeid(pB1).name()<<endl; // class B *

     C *pC1 = new C();
     C c2;

     cout<<typeid(pC1).name()<<endl; // struct C *
     cout<<typeid(c2).name()<<endl; // struct C

     return 0;
}

是的,对于我们自定义的结构体和类,tpyeid都能支持。
在上面的代码中,在调用完typeid之后,都会接着调用name()函数,可以看出typeid函数返回的是一个结构体或者类,然后,再调用这个返回的结构体或类的name成员函数。

  • typeid的返回是type_info类型

  • type_info类:这个类的确切定义是与编译器实现相关的,因为type_info类的复制构造函数和赋值运算符都是私有的,所以不允许用户自已创建type_info的对象,比如type_info A;错误,没有默认的构造函数。

    唯一要使用type_info类的方法就是使用typeid函数。

class type_info
{
public:
    virtual ~type_info();
    bool operator==(const type_info& _Rhs) const; // 用于比较两个对象的类型是否相等
    bool operator!=(const type_info& _Rhs) const; // 用于比较两个对象的类型是否不相等
    bool before(const type_info& _Rhs) const;

    // 返回对象的类型名字,这个函数用的很多
    const char* name(__type_info_node* __ptype_info_node = &__type_info_root_node) 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);
    static const char * _Name_base(const type_info *,__type_info_node* __ptype_info_node);
    static void _Type_info_dtor(type_info *);
};

在type_info类中,复制构造函数和赋值运算符都是私有的,同时也没有默认的构造函数;所以,我们没有办法创建type_info类的变量,例如type_info A;这样是错误的。那么typeid函数是如何返回一个type_info类的对象的引用的呢?

dynamic_cast操作符,将基类类型的指针或引用安全地转换为派生类型的指针或引用。这里就要对于dynamic_cast的内幕一探究竟。首先来看一段代码:

#include <iostream>
#include <typeinfo>
using namespace std;

class A
{
public:
     virtual void Print() { cout<<"This is class A."<<endl; }
};

class B
{
public:
     virtual void Print() { cout<<"This is class B."<<endl; }
};

class C : public A, public B
{
public:
     void Print() { cout<<"This is class C."<<endl; }
};

int main()
{
     A *pA = new C;
     //C *pC = pA; // Wrong
     C *pC = dynamic_cast<C *>(pA);
     if (pC != NULL)
     {
          pC->Print();
     }
     delete pA;
}

在上面代码中,如果我们直接将pA赋值给pC,这样编译器就会提示错误,而当我们加上了dynamic_cast之后,一切就ok了。那么dynamic_cast在后面干了什么呢?

dynamic_cast主要用于在多态的时候,它允许在运行时刻进行类型转换,从而使程序能够在一个类层次结构中安全地转换类型,把基类指针(引用)转换为派生类指针(引用)。

当类中存在虚函数时,编译器就会在类的成员变量中添加一个指向虚函数表的vptr指针,每一个class所关联的type_info object也经由virtual table被指出来,通常这个type_info object放在表格的第一个slot。

当我们进行dynamic_cast时,编译器会帮我们进行语法检查。如果指针的静态类型和目标类型相同,那么就什么事情都不做;否则,首先对指针进行调整,使得它指向vftable,并将其和调整之后的指针、调整的偏移量、静态类型以及目标类型传递给内部函数。其中最后一个参数指明转换的是指针还是引用。

两者唯一的区别是,如果转换失败,前者返回NULL,后者抛出bad_cast异常。对于在typeid函数的使用中所示例的程序,我使用dynamic_cast进行更改,代码如下:

#include <iostream>
#include <typeinfo>
using namespace std;

class A
{
public:
     virtual void Print() { cout<<"This is class A."<<endl; }
};

class B : public A
{
public:
     void Print() { cout<<"This is class B."<<endl; }
};

class C : public A
{
public:
     void Print() { cout<<"This is class C."<<endl; }
};

void Handle(A *a)
{
     if (dynamic_cast<B*>(a))
     {
          cout<<"I am a B truly."<<endl;
     }
     else if (dynamic_cast<C*>(a))
     {
          cout<<"I am a C truly."<<endl;
     }
     else
     {
          cout<<"I am alone."<<endl;
     }
}

int main()
{
     A *pA = new B();
     Handle(pA);
     delete pA;
     pA = new C();
     Handle(pA);
     return 0;
}

标签:info,解析,const,类型,Cpp,RTTI,type,指针
From: https://www.cnblogs.com/RioTian/p/17746167.html

相关文章

  • 微服务架构的现状与未来:服务网格与云原生趋势解析
    文章目录微服务架构的崛起服务网格的崭露Istio和EnvoyLinkerd云原生技术的崭露KubernetesHelm未来趋势更强大的服务网格更智能的自动化更紧密的云原生集成结论......
  • 递归解析Json,实现生成可视化Tree+快速获取JsonPath
    内部平台的一个小功能点的实现过程,分享给大家:递归解析Json,可以实现生成可视化Tree+快速获取JsonPath。步骤:1.利用JsonPath读取根,获取JsonObject2.递归层次遍历JsonObjec,保存结点信息3.利用zTree展示结点为可视化树,点击对应树的结点即可获取对应结点的JsonPath1.利用JsonPath......
  • 访问远程zip并解析csv
    publicList<Info>exportsCode(StringorderNo){List<Info>infoResponses=newArrayList<Info>();Stringtoken=queryToken();if(StringUtils.isBlank(token)){returnexportsCod......
  • Windows桌面应用程序源文件.cpp注释
     这个是visualstudio2022上利用Windows桌面应用程序模板创建的源文件注释一个Windows图形界面(GUI)应用程序通常由主窗体,对话框,控件组成。当应用程序创建一个窗体,需要调用CreateWindowEx函数,必须提供的参数1.窗体类窗体类是一个结构体。是一系列属性的集合,用来描述窗体的行为......
  • 网络规划设计师真题解析--TCP慢启动拥塞避免机制
    TCP使用慢启动拥塞避免机制进行拥塞控制。当拥塞窗口大小为16时,发送节点出现超时未收到确认现象时,将采取的措施是(26)。再经过5轮后的拥塞窗口大小为(27)。26、A.将慢启动阈值设为16,将拥塞窗口设为8,并进入拥塞避免阶段B.将慢启动阈值设为16,将拥塞窗口设为1,并进入慢开始阶段C.将慢启动......
  • 网络规划设计师真题解析--TCP慢启动拥塞避免机制
    TCP使用慢启动拥塞避免机制进行拥塞控制。当拥塞窗口大小为16时,发送节点出现超时未收到确认现象时,将采取的措施是(26)。再经过5轮后的拥塞窗口大小为(27)。26、A.将慢启动阈值设为16,将拥塞窗口设为8,并进入拥塞避免阶段B.将慢启动阈值设为16,将拥塞窗口设为1,并进入慢开始阶段C.将慢启动阈......
  • 数据库事务和隔离级别的解析
    什么是数据库中的事务,可以说事务就是一组原子性的SQL查询,独立的工作单元。我们的事务内的语句,要么全部执行成功,要么全部执行失败!事务要满足ACID特性,可以通过Commit提交一个事务,也可以使用Rollback进行回滚!下面我们就介绍一下事务的ACID特性。ACID特性原子性(actomicity)一个事......
  • 大数据与AI:解析智慧城市的幕后英雄
    文章目录1.智慧城市的定义与发展2.大数据:智慧城市的基石2.1大数据的概念与重要性2.2大数据的应用案例2.2.1智能交通管理2.2.2能源效率优化2.2.3城市规划与土地利用3.人工智能:智慧城市的大脑3.1人工智能的概念与重要性3.2人工智能的应用案例3.2.1智能垃圾分类3.2.2智能......
  • MaSuRCA 软件安装 swig/perl5/swig_wrap.cpp:342:20: fatal error: string.h: No such
     001、问题MaSuRCA软件安装swig/perl5/swig_wrap.cpp:342:20:fatalerror:string.h:Nosuchfileordirectory  002、原因,当前环境处于conda的base环境,可能是函数库调用混乱。  003、解决方法,推出conda基础环境安装(base)[b20223040323@admin1MaSuRCA-4......
  • 计算机网络之DNS解析过程
    一、什么是DNSDNS(DomainNameSystem),域名解析系统,它的作用就是域名和IP相互映射。二、域名解析过程假设要查询www.baidu.com的IP地址:1、首先会查找浏览器缓存,看看能否找到www.baidu.com对应的IP地址,找到就直接返回;否则进行下一步。2、将请求发往本地DNS服务器,如果查找到也直接返回,......