【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 ...
随机推荐
- day2-JS基础&流程控制
typora-root-url: img 自增自减运算符 1.基本使用 内置提供 ++.--运算符 是用于将变量本身进行加1或者减1操作 // 1.基本使用 var i = 10; i++;//等价于 ...
- 浏览器跨 Tab 窗口通信原理及应用实践
最近,相信大家一定被这么个动效给刷屏了: 以至于,基于这个效果的二次创作层出不穷,眼花缭乱. 基于跨窗口通信的弹弹球: 基于跨窗口通信的 Flippy Bird: 我也尝试制作了一个跨 Tab 窗口的 ...
- 【Android】实现连接SQLite并尝试进行增删改查
- Java实现相似结构表算法
[产品需求] 对所有元数据进行分析,匹配出表字段相似度达到阈值的向相似结构表关系数据. 网上没有搜到相关算法实现,只能自己动手了. [算法实现] 简单点实现的话,可以轮询所有表,每张表都和其它表进行匹 ...
- [USACO2007OPENG] Dining G
题目描述 Cows are such finicky eaters. Each cow has a preference for certain foods and drinks, and she w ...
- Activity发送信息给Fragment
在MainActivity中设置发送的信息,在fragment中接收, @Override public void onClick(View view) { switch (view.getId()) ...
- k8s安装etcd出现Job for etcd.service failed......"journalctl -xe" for details.
错误如下 先按照提示,输入 journalctl -xe 看一些详细信息 1.针对:start request repeated too quickly for etcd.service 错误,解决办 ...
- EEPROM M24C64替换AT24C64出现读取数据为0xff情况解决办法
EEPROM M24C64替换AT24C64出现读取数据为0xff情况解决办法 硬件情况 STM32F103CBT6+模拟IIC,主频72MHz,IIC上拉电阻3.3kΩ 出现原因 在IIC停止信号上 ...
- 小程序优化:第三方SDK过大解决方案
[前言] 小程序开发中,有时会遇到下面这种情况,项目目录中存放过大的js包,会被警告影响手机端性能,同时让开发编译启动变得很慢.慢是其次,单是影响性能这一点,就需要解决一下. [云资源] 将项目js包 ...
- ElasticSearch之禁用交换分区
操作系统将进程加载至内存中执行时,对于当前未使用到的内存页,可能会将相关内存页交换至硬盘上,即swap. 对于性能敏感.时延敏感的应用程序比如ElasticSearch,swap特性会明显影响性能和稳 ...