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;
}

【Cpp】RTTI 机制原理解析的更多相关文章

  1. Android Handler 消息机制原理解析

    前言 做过 Android 开发的童鞋都知道,不能在非主线程修改 UI 控件,因为 Android 规定只能在主线程中访问 UI ,如果在子线程中访问 UI ,那么程序就会抛出异常 android.v ...

  2. 关于iOS URL缓存机制原理解析

    关于URL缓存机制中   利用request对象判断是否缓存   其实request是否相等的判断依据是URLString是否相等

  3. Kafka设计解析(八)- Exactly Once语义与事务机制原理

    原创文章,首发自作者个人博客,转载请务必将下面这段话置于文章开头处. 本文转发自技术世界,原文链接 http://www.jasongj.com/kafka/transaction/ 写在前面的话 本 ...

  4. Kafka设计解析(八)Exactly Once语义与事务机制原理

    转载自 技术世界,原文链接 Kafka设计解析(八)- Exactly Once语义与事务机制原理 本文介绍了Kafka实现事务性的几个阶段——正好一次语义与原子操作.之后详细分析了Kafka事务机制 ...

  5. C++中的RTTI机制解析

    RTTI RTTI概念 RTTI(Run Time Type Identification)即通过运行时类型识别,程序能够使用基类的指针或引用来检查着这些指针或引用所指的对象的实际派生类型. RTTI ...

  6. 剖析Qt的事件机制原理

    版权声明 请尊重原创作品.转载请保持文章完整性,并以超链接形式注明原始作者“tingsking18”和主站点地址,方便其他朋友提问和指正. QT源码解析(一) QT创建窗口程序.消息循环和WinMai ...

  7. Delphi 的RTTI机制浅探3(超长,很不错)

    转自:http://blog.sina.com.cn/s/blog_53d1e9210100uke4.html 目录========================================== ...

  8. Web APi之过滤器创建过程原理解析【一】(十)

    前言 Web API的简单流程就是从请求到执行到Action并最终作出响应,但是在这个过程有一把[筛子],那就是过滤器Filter,在从请求到Action这整个流程中使用Filter来进行相应的处理从 ...

  9. Android中插件开发篇之----应用换肤原理解析

    一.前言 今天又到周末了,感觉时间过的很快呀.又要写blog了.那么今天就来看看应用的换肤原理解析.在之前的一篇博客中我说道了Android中的插件开发篇的基础:类加载器的相关知识.没看过的同学可以转 ...

  10. Volley 实现原理解析(转)

    Volley 实现原理解析 转自:http://blog.csdn.net/fengqiaoyebo2008/article/details/42963915 1. 功能介绍 1.1. Volley ...

随机推荐

  1. Unity学习笔记--数据持久化之PlayerPrefs的使用

    数据持久化 PlayerPrefs相关 PlayerPrefs是Unity游戏引擎中的一个类,用于在游戏中存储和访问玩家的偏好设置和数据.它可以用来保存玩家的游戏进度.设置选项.最高分数等信息.Pla ...

  2. ClickHouse(16)ClickHouse日志引擎Log详细解析

    日志引擎系列 这些引擎是为了需要写入许多小数据量(少于一百万行)的表的场景而开发的. 这系列的引擎有: StripeLog Log TinyLog 共同属性 引擎: 数据存储在磁盘上. 写入时将数据追 ...

  3. 通过 VS Code 优雅地编辑 Pod 内的代码(非 NodePort)

    目录 1. 概述 2. NodePort 方式 3. Ingress 方式 4. 救命稻草 5. 其他 1. 概述 今天聊点啥呢,话说,你有没有想过怎样用 VS Code 连上 K8s 集群内的某个 ...

  4. scroll-view和swiper的使用

    源码: <template>            <viex class="out">            <view class="b ...

  5. docker开启或关闭<开机自启容器>

    启动容器时设置 docker run --restart=always 启动完成也可以修改 docker update --restart=always <容器ID> 想取消容器自启 do ...

  6. MySQL调优的一些总结

    SQL优化可以从那几个方面去优化 1.基本写法优化: 1.少使用select * ,尽量使用具体字段: 2.对于条件来说等号之类两边的字段类型要一致,字符串不加单引号索引会失效: 3.尽量少使用Ord ...

  7. LeetCode5716:好因子的最大数目(数学、快速幂)

    解题思路:因为primeFactors比较大,所以需要使用快速幂. class Solution: def quick_pow(self,base,x): ans = 1 while x>0: ...

  8. 工具类图片转base64

    工具类图片转base64 import sun.misc.BASE64Encoder; import java.io.FileInputStream; import java.io.IOExcepti ...

  9. 内网& 公网

    内.外网是相对于防火墙而言的,在防火墙内部叫做内网,反之就是外网.在一定程度上外网等同于公网,内网等同于私网. 内网IP是什么? 内网IP简单理解就是局域网IP地址.内网地址即局域网(LAN),内网的 ...

  10. rcs群发软件系统功能设计与应用,rcs群发软件系统,rcs群发软件

    随着科技的不断发展,人们对于通讯方式的需求也在不断变化,传统的短信.电话已经无法满足人们对于高效.便捷.实时的通讯需求,正是在这样的背景下,富通讯解决方案(Rich Communication Sui ...