MFC中的RTTI(Runtime Type Identification, 运行时类型识别)详解(参考《深入浅出MFC》)
在MFC中的RTTI的实现,主要是利用一个名为CRuntimeClass的结构来链接各个“有关系的类”的信息来实现的。简单来说,就是在需要用到RTTI技术的类内建立CRuntimeClass的静态变量,来储存该类的相关信息(包括类名、基类的CRuntimeClass结构的指针、让“有关系的类”的信息形成链表的next指针、以及链表的首指针(静态)等信息),这里的“有关系”指的是从同一个基类继承下来的所有的类之间的关系。这些信息(CRuntimeClass)节点的链接过程,简化如下:
(注意: 某个类 (如,CObject) 的CRuntimeClass的变量名就是class接上其类名称 (如:classCObejct) )
1.建立使用 RTTI 技术的基类时,新建信息链表。
新建一个需要用到RTTI技术的基类的时候,需要我们创建一个类似图1的CRuntimeClass结构,实际上是初始化了管理所有和该类“有关系”的类的信息的链表,其中,CRuntimeClass的静态变量pFirstClass就是指向该信息链表的首节点的。
2.建立子类时,将子类信息(子类的CRuntimeClass结构)关联到信息链表。(CCmdTarget 继承自 CObject)
图2
将子类的信息也加入到链表中,这时候pFirstClass指针将会指向新的信息。模拟代码如下,
CCmdTarget::classCCmdTarget.next = CRuntimeClass::pFirstClass;
CRuntimeClass::pFirstClass = pCCmdTarget::classCCmdTarget;
3.后面新建子类的情况类似,直接上图。
a) CWinThread继承自CCmdTarget,将其信息节点(classCWinThread)加入链表中。
图3
b) CWnd继承自CCmdTarget,将其信息节点(CWnd::classCWnd)加入信息链表中。
图4
正是由于存在有这样的链表将“有关系的类”的信息(CRuntimeClass结构)关联起来,因此,我们能够很轻松地实现RTTI的相关功能。
| RTTI提供了以下两个非常有用的功能: (1)查询指针和引用所指的实际类型。 (2)将基类类型的指针或引用安全地转换为派生类型的指针或引用。 |
知道了在宏观上的控制,下面我们来仔细解刨一下,MFC在细节处是怎么落实RTTI技术的。
首先,我们来看看每一个信息节点是怎么定义的。
前面提到了,MFC中每一个使用RTTI的类都会创建一个CRuntimeClass的对象来储存对象的信息,“有点关系的类”之间通过这个结构形成的链表关联起来。在侯捷(侯俊杰)先生的《深入浅出MFC(第二版)》中是这样仿真该结构(CRuntimeClass)的。
struct CRuntimeClass
{
LPCSTR m_lpszClassName; //包含该CRuntimeClass对象的类的名称。
int m_nObjectSize; //m_lpszClassName占用的字节数
UINT m_wSchema; //分类编号
CObject* (PASCAL* m_pfnCreateObject)(); //NULL => abstract class,存包含该结构对象的类的构造函数的指针
CRuntimeClass* m_pBaseClass; //包含该结构对象的类的基类的CRuntimeClass对象指针
//下面是构成链表所需要的变量
static CRuntimeClass* pFirstClass; //链表的首节点
CRuntimeClass* m_pNextClass; //该节点的下一个节点。
}
我们知道,在MFC中要用到RTTI时,总是会使用DECLARE_DYNAMIC/IMPLEMENT_DYNAMIC宏,正是这一对宏,链接“有关系的类”的链表。既然说是“一对”,当然是要成对使用的了,一个用于声明,一个用于定义,你挑柴来我织布,夫妻双双把家还。 = =!
在具体讨论源代码前,先要知道,宏定义中用#把宏参数变为一个字符串,用##把两个宏参数贴合在一起。
如,
#include <iostream>
using namespace std;
#define TEST(X) #X
int main()
{
cout << TEST(TEST) << endl;
}
输出:
图5
再如,
#include <iostream>
using namespace std;
#define TEST(X, Y) X##Y
int main()
{
cout << TEST("X#", "#Y") << endl;
}
输出:
图6
好了,有了上面一系列的讨论,现在我们来专专心心分析一下MFC中的RTTI究竟是怎么实现滴。 +_-!
首先是声明,声明的宏DECLARE_DYNAMIC的定义如下:
#define DECLARE_DYNAMIC(class_name) \
public: \
static CRuntimeClass class##classname; \
virtual CRuntimeClass* GetRuntimeClass() const;
前面提到,要用到RTTI必须要创建一个静态的CRuntimeClass结构对象,从这个声明的宏,我们可以知道,定义了一个名为class+类名的CRuntimeClass对象,以及回去该对象地址的虚函数。之所以为虚函数,是因为子类和父类返回的CRuntimeClass不一样。
其次就是实现的宏IMPLEMENT_DYNAMIC,这个宏稍微有一点乱,烦请认真地阅读,实在看不懂的话,也没关系,代码后面我有详细地解读。嘿嘿……闲话少说,上代码。
#define IMPLEMENT_DYNAMIC(class_name, base_class_name)\
_IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, 0xFFFF, NULL)
#define _IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, wSchema, pfnNew)\
static char _lpsz##class_name[] = #class_name; \
CRuntimeClass class_name::class##class_name = {\
_lpsz##class_name, sizeof(class_name), wSchema, pfnNew, \
RUNTIME_CLASS(base_class_name), NULL }; \
static AFX_CLASSINIT _init_##class_name(&class_name::class##class_name);\
CRuntimeClass* class_name::GetRuntimeClass() const \
{ return &class_name::class##class_name; }\
#define RUNTIME_CLASS(class_name) (&class_name::class##class_name)
怎么样?被吓着了没?如果你说No的话,那就推荐你直接去看MFC这一段的源码。
前两行代码,相当于偷梁换柱,这个没什么说的,看得懂宏定义的,都懂!
_IMPLEMENT_RUNTIMECLASS宏,实际上是完成了对DECLARE_DYNAMIC宏声明的CRuntimeClass变量class##class_name的初始化,以及对GetRuntimeClass()函数的定义。下面是截取的部分代码。
static char _lpsz##class_name[] = #class_name; //获取包含该CRuntimeClass的类的名称
CRuntimeClass class_name::class##class_name = {\ //初始化CRuntimeClass的参数
_lpsz##class_name, sizeof(class_name), wSchema, pfnNew, \
RUNTIME_CLASS(base_class_name), NULL }; \
在_IMPLEMENT_RUNTIMECLASS宏中用到了AFX_CLASSINIT 结构来对CRuntimeClass进行初始化,使之链接上存有类的信息链表。AFX_CLASSINIT 它的构造函数实现对CRuntimeClass对象class##class_name进行操作。
struct AFX_CLASSINIT
{
AFX_CLASSINIT(CRuntimeClass* pNewClass)
{
pNewClass->m_pNextClass = CRuntimeClass::pFirstClass;
CRuntimeClass::pFirstClass = pNewClass;
}
};
虽然可能还没大讲清楚,但是,大概就是这个意思了。还是上个例子吧。
在头文件中,我们用DECLARE_DYNAMIC进行声明。
// In *.h file.
class CWnd : public CCmdTarget
{
public:
CWnd(){}
~CWnd(){}
DECLARE_DYNAMIC(CWnd)
/* 上面的DECLARE_DYNAMIC(CWnd),展开就成为:
public:
static CRuntimeClass classCWnd; //声明静态变量
virtual CRuntimeClass* GetRuntimeClass() const;
*/
};
在源文件中,我们用IMPLEMENT_DYNAMIC进行实现。
// In *.cpp file
IMPLEMENT_DYNAMIC(Cwnd, CCmdTarget)
/*IMPLEMENT_DYNAMIC(Cwnd, CCmdTarget),展开就成为:
static char _lpszCWnd[] = "CWnd";
//定义并初始化静态变量classCWnd
CRuntimeClass classCWnd = { _lpszCWnd, sizeof(CWnd), 0xFFFF, NULL,
&CCmdTarget::classCCmdTarget, NULL }
//定义并初始化静态变量_init_CWnd,实际上是在调用其构造函数,将classCWnd连接到链表。
static AFX_CLASSINIT _init_CWnd(&CWnd::classCWnd);
//获得CRuntimeClass对象classCWnd的指针
CRuntimeClass* CWnd::GetRuntimeClass() const
{
return &CWnd::classCWnd;
}
*/
好了,相信坚持看到这里的小伙伴已经大概了解MFC关于RTTI的最重要的两个宏了吧?关于MFC的RTTI其实还有很多东西可以讨论的,要是有时间的话,我会继续更新相关的内容。\(^o^)/~,好吧,最后来一句:
【参考文献】
1.侯捷著《深入浅出MFC(第二版)》
2. MSDN相关内容
MFC中的RTTI(Runtime Type Identification, 运行时类型识别)详解(参考《深入浅出MFC》)的更多相关文章
- MFC六大核心机制之二:运行时类型识别(RTTI)
上一节讲的是MFC六大核心机制之一:MFC程序的初始化,本节继续讲解MFC六大核心机制之二:运行时类型识别(RTTI). typeid运算子 运行时类型识别(RTTI)即是程序执行过程中知道某个对象属 ...
- C++ - RTTI(RunTime Type Information)执行时类型信息 具体解释
RTTI(RunTime Type Information)执行时类型信息 具体解释 本文地址: http://blog.csdn.net/caroline_wendy/article/details ...
- RTTI (Run-Time Type Identification,通过运行时类型识别) 转
参考一: RTTI(Run-Time Type Identification,通过运行时类型识别)程序能够使用基类的指针或引用来检查这些指针或引用所指的对象的实际派生类型. RTTI提供了以下两个 ...
- (C/C++学习笔记) 二十三. 运行时类型识别
二十三. 运行时类型识别 ● 定义 运行时类型识别(Run-time Type Identification, RTTI) 通过RTTI, 程序能够使用基类的指针或引用来检查(check)这些指针或引 ...
- 框架原理第二讲,RTTI,运行时类型识别.(以MFC框架讲解)
框架原理第二讲,RTTI,运行时类型识别.(以MFC框架讲解) 一丶什么是RTTI,以及RTTI怎么设计 通过第一讲,我们知道了怎么样升成一个窗口了,以及简单的消息循环. 第二讲则是主要讲解RTTI ...
- MFC原理第三讲.RTTI运行时类型识别
MFC原理第三讲.RTTI运行时类型识别 一丶什么是RTTI RTTI. 运行时的时候类型的识别. 运行时类型信息程序.能够使用基类(父类)指针 或者引用 来检查这些指针或者引用所指的对象. 实际派生 ...
- 《深入浅出MFC》系列之运行时类型识别(RTTI)
/********************************************************************************** 发布日期:2017-11-13 ...
- Java基础之RTTI 运行时类型识别
运行时类型识别(RTTI, Run-Time Type Identification)是Java中非常有用的机制,在Java运行时,RTTI维护类的相关信息. 多态(polymorphism)是基于R ...
- RTTI(运行时类型识别)
运行时类型识别(Run-time type identification , RTTI),是指在只有一个指向基类的指针或引用时,确定所指对象的准确类型的操作.其常被说成是C++的四大扩展之一(其他三个 ...
- C++之运行时类型识别RTTI
C++ Code 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849 ...
随机推荐
- PEP8语法规范解释说明
PEP8规范解析 内容概要: 1.PEP8规范是什么? 2.PEP8相关内容 1.PEP8规范是什么 PEP是Python Enhancement Proposal的缩写,翻译为:"Pyth ...
- 2022csp普及组真题:乘方(pow)
2022csp普及组真题:乘方(pow) 题目 [题目描述] 小文同学刚刚接触了信息学竞赛,有一天她遇到了这样一个题:给定正整数 a 和 b ,求 a^b 的值是多少. a^b 即 b 个 a 相乘的 ...
- 进军东南亚市场,腾讯云数据库 TDSQL 助力印尼 BNC 银行数字化转型
腾讯云数据库在助力金融核心系统分布式替换上,已经辐射到了东南亚市场. 东南亚最大的银行之一印尼BNC银行(Bank Neo Commerce)已正式完成新核心分布式迁移,使用腾讯云数据库TDSQL后, ...
- js-day01-商品订单信息
学会表格表单(html+css) 表格的默认CSS属性 *{ margin: 0; padding: 0; } tabl ...
- 【Java技术】String类的使用
属于引用类型,在java.lang包下,类似的还有Integer.Character.Boolean.Math 常用方法: char charAt(int index)返回 char指定索引处的值. ...
- Node.js躬行记(25)——Web自动化测试
网页在提测流转给 QA 后,如何能帮他们更有效而准确的完成测试,是我一直在思考的一个问题. QA 他们会对网页编写测试用例,在提测之前会让我们将优先级最高的用例跑通,这在一定程度上能够避免频繁的返工, ...
- k8s篇-k8s集群架构及组件详解【史上最详细】
O kubernetes简介 k8s是什么 k8s是一个可移植的.可扩展的开源平台,用于管理容器化的工作负载和服务,可以促进声明式配置和自动化. k8s能做什么 1)服务发现和负载均衡 Kuberne ...
- <二>线程间互斥-mutex互斥锁和lock_guard
多线程程序 竞态条件:多线程程序执行的结果是一致的,不会随着CPU对线程不同的调用顺序而产生不同的运行结果. 解决?:互斥锁 mutex 经典的卖票问题,三个线程卖100张票 代码1 #include ...
- 从Spring中学到的【2】--容器类
容器类 我们在实际编码中,常常会遇到各种容器类,他们有时叫做POJO,有时又叫做DTO,VO, DO等,这些类只具有容器的作用,具有完全的get,set方法,作为信息载体,作数据传输用. 其实,很多地 ...
- Vue中实现自定义excel下载
目录 第一种:后端生成excel 第二种:前端合成excel 总结 参考资料 最近在工作中遇到一个需求,就是需要在前端实现一个错误模板Excel的下载功能. 实现下载有两种方式,一种是后端生成一个ex ...