【Cpp】RTTI 机制原理解析
References
- Baidu Wiki
- C++中的RTTI机制详解
- RTTI
- 推荐阅读: RTTI 原理
- 推荐阅读:C++中的RTTI机制
什么是RTTI机制?
RTTI 是“Runtime Type Information”的缩写,意思是:运行时类型信息。它提供了运行时确定对象类型的方法。
RTTI 通过运行时类型信息程序能够使用基类的指针或引用来检查这些指针或引用所指的对象的实际派生类型。
听起来很像包含虚函数的多态机制,这里和虚函数又有哪些不同呢?
为什么需要 RTTI 机制
数组是十分常用的数据结构,而对经常使用C++的同学来说指针也是逃不开的拦路虎。相信在很多课程或书本上大家都看过说指针在内存中存储的是一个地址,而数组名也是一个地址。
和很多其他语言一样,C++是一种静态类型语言。其数据类型是在编译期就确定的,不能在运行时更改。然而由于面向对象程序设计中多态性的要求,C++中的指针或引用(Reference)本身的类型,可能与它实际代表(指向或引用)的类型并不一致。
有时我们需要将一个多态指针转换为其实际指向对象的类型,就需要知道运行时的类型信息,这就产生了运行时类型识别的要求。
在C++中存在虚函数,也就存在了多态性,对于多态性的对象,在程序编译时可能会出现无法确定对象的类型的情况。
当类中含有虚函数时,其基类的指针就可以指向任何派生类的对象,这时就有可能不知道基类指针到底指向的是哪个对象的情况,类型的确定要在运行时利用运行时类型标识做出。为了获得一个对象的类型可以使用 typeid 函数,该函数反回一个对type_info类对象的引用,要使用 typeid 必须使用头文件 typeinfo。
C++中如何实现RTTI机制?
C++提供了两个关键字 typeid 和dynamic_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 机制原理解析的更多相关文章
- Android Handler 消息机制原理解析
前言 做过 Android 开发的童鞋都知道,不能在非主线程修改 UI 控件,因为 Android 规定只能在主线程中访问 UI ,如果在子线程中访问 UI ,那么程序就会抛出异常 android.v ...
- 关于iOS URL缓存机制原理解析
关于URL缓存机制中 利用request对象判断是否缓存 其实request是否相等的判断依据是URLString是否相等
- Kafka设计解析(八)- Exactly Once语义与事务机制原理
原创文章,首发自作者个人博客,转载请务必将下面这段话置于文章开头处. 本文转发自技术世界,原文链接 http://www.jasongj.com/kafka/transaction/ 写在前面的话 本 ...
- Kafka设计解析(八)Exactly Once语义与事务机制原理
转载自 技术世界,原文链接 Kafka设计解析(八)- Exactly Once语义与事务机制原理 本文介绍了Kafka实现事务性的几个阶段——正好一次语义与原子操作.之后详细分析了Kafka事务机制 ...
- C++中的RTTI机制解析
RTTI RTTI概念 RTTI(Run Time Type Identification)即通过运行时类型识别,程序能够使用基类的指针或引用来检查着这些指针或引用所指的对象的实际派生类型. RTTI ...
- 剖析Qt的事件机制原理
版权声明 请尊重原创作品.转载请保持文章完整性,并以超链接形式注明原始作者“tingsking18”和主站点地址,方便其他朋友提问和指正. QT源码解析(一) QT创建窗口程序.消息循环和WinMai ...
- Delphi 的RTTI机制浅探3(超长,很不错)
转自:http://blog.sina.com.cn/s/blog_53d1e9210100uke4.html 目录========================================== ...
- Web APi之过滤器创建过程原理解析【一】(十)
前言 Web API的简单流程就是从请求到执行到Action并最终作出响应,但是在这个过程有一把[筛子],那就是过滤器Filter,在从请求到Action这整个流程中使用Filter来进行相应的处理从 ...
- Android中插件开发篇之----应用换肤原理解析
一.前言 今天又到周末了,感觉时间过的很快呀.又要写blog了.那么今天就来看看应用的换肤原理解析.在之前的一篇博客中我说道了Android中的插件开发篇的基础:类加载器的相关知识.没看过的同学可以转 ...
- Volley 实现原理解析(转)
Volley 实现原理解析 转自:http://blog.csdn.net/fengqiaoyebo2008/article/details/42963915 1. 功能介绍 1.1. Volley ...
随机推荐
- 期望最大化(EM)算法:从理论到实战全解析
本文深入探讨了期望最大化(EM)算法的原理.数学基础和应用.通过详尽的定义和具体例子,文章阐释了EM算法在高斯混合模型(GMM)中的应用,并通过Python和PyTorch代码实现进行了实战演示. 关 ...
- 2023第十四届极客大挑战 — MISC WP
Misc方向题解:来自本人 cheekin 请前往"三叶草小组Syclover"微信公众号输入flag获得flag 我的解答: 关注公众号回复就可以得到一张图片,图片隐写zsteg ...
- Kubernetes 中的服务注册与发现原理分析
公众号「架构成长指南」,专注于生产实践.云原生.分布式系统.大数据技术分享. 对k8s有点了解技术人员,应该都只知道k8s是有服务注册发现的,今天就分析下这个原理,看看怎么实现的. 什么是服务注册与发 ...
- Spring系列:基于Spring-AOP和Spring-Aspects实现AOP切面编程
目录 一.概念及相关术语 概念 相关术语 ①横切关注点 ②通知(增强) ③切面 ④目标 ⑤代理 ⑥连接点 ⑦切入点 作用 二.基于注解的AOP 技术说明 准备工作 创建切面类并配置 各种通知 切入点表 ...
- MySQL查询语句执行顺序
注意:理论上select后面的字段别名是不可以在where group by having 等后面使用的,但是MySQL5.7做了相应的优化,group by having 后面可以使用
- Oracle表空间和数据文件
表空间:tablespace 表空间就是:存放数据库表.索引.等等对象的逻辑空间. oracle数据在安装并创建实例后,默认会自动创建多个表空间. ORACL默认表空间 SYSTEM表空间 存放ora ...
- Python subprocess 使用(一)
Python subprocess 使用(一) 本文主要讲下 subprocess 的简单使用. 1: 通过subprocess 获取设备信息 import subprocess def get_an ...
- Unity无法显示animator面板,如何解决?
步骤: 点击动画的主体: 右侧Inspector面板找到Animator,双击Controller中的对象: 左上角即可显示animator面板. 总结: 不行就双击!!!!!!!!!!!!!!!!! ...
- C语言基础之四舍五入
要求:输入任意的2个小数:将这2个小数相加并显示结果:将结果按四舍五入方法转换成整数并显示. 0.0到0.4的数加上0.5不会进位,而0.5到0.9的数加上0.5会进位.所以可以依靠这个特点让计算后的 ...
- 初探Git:理解和使用版本控制的魔法
遥远的古代,有一位美丽的仙女叫做嫦娥.她的丈夫后羿获得了令人长生不老的鹿骨露.一天,嫦娥在好奇心的驱使下,独自偷喝了这瓶仙药. 喝下仙药的瞬间,嫦娥发现自己开始飘起,越飘越高,最后飘向了月亮.嫦娥惊慌 ...