MFC中的动态创建与运行时识别

MFC中支持运行时类型识别与动态创建。这更多的是设计问题。理解了其中的设计,可以更快定位用MFC框架开发的代码。通过研究MFC实现这些的细节,可以更快地掌握其他开发框架的设计思想。

宏定义中的"#"与 “##”
MFC框架大量使用了宏定义来“自动”完成框架的声明与实现工作。因此首先复习下宏中的#与##功能是有必要的。

“#”
单独的一个#符号,作用是“字符串化”,比如:

#define ToString(x) #x
则有:

int main()
{
//相当于printf("%s\r\n", “Hello, world!”);
printf("%s\r\n", ToString(Hello, world!);
}
有些框架中会利用这样的字符串化,形成字符常量到字符串的映射,比如,用这样的方法制作消息响应表:

struct tagMsgMap {
int nMsg;
char *szMsg;
};

#define MSG_ENTRY(Msg) {Msg, #Msg}

tagMsgMap g_MsgMapEntry[]= {
MSG_ENTRY(WM_COMMAND),
MSG_ENTRY(WM_KEYUP),

};
“##”

"##"的作用常被解释为“连接符”,其实从其存在目的解释,说成“分隔符”也不算错。因为设计它的目的之一就是用来解决某些情况下不能用空格来分隔宏定义的问题。比如:

#define PRE(name) prefix_##name
以上的例子,若将##换成空格,则prefix_与name之间会有空格,不符合我们期待的“PRE(haha)等价于prefix_haha”。

总结一下:##起到代替空格的分隔作用,并且在宏展开时,会消除自身前面的空格,如:

#define MACRO1(name, type) type name_##type##type
#define MACRO2(name, type) type name##
##type##_type

/* 等价于: int name_int_type; */
MACRO1(a1, int);

/* 等价于: int a1_int_type; */
MACRO2(a1, int);
因为##分隔的位置不同,导致编译器预处理对TOKEN的识别也不同。并且,因为##为消息自身前面的空格,因此以上定义的宏可以写成以下:

#define MACRO1(name, type) type name_ ##type ##type
#define MACRO2(name, type) type name ##
##type ##_type
设计
MFC的类都有IsKindOf()这个成员函数,用来判断自己是否属于某个类(或其派生类),要达到这个目的很简单,可以将所有的派生关系形成一颗单向树,识别类型时,从节点向根遍历并作判断。

此外,MFC还支持从字符串创建类(这也是序列化的必要条件),这就需要有一个全局的容器,里面装有所有类的创建函数。

为了满足以上两条,MFC设计了CRuntimeClass类来记录支持RTTI及动态创建的类的信息,其简化定义如下:

struct CRuntimeClass
{
// Attributes
LPCSTR m_lpszClassName; //类名称
int m_nObjectSize; //类大小
UINT m_wSchema; // 版本号
CObject* (PASCAL* m_pfnCreateObject)(); // 对象创建函数
CRuntimeClass* m_pBaseClass; //基类信息,各CRuntimeClass通过它连成单向树

    // CRuntimeClass objects linked together in simple listCRuntimeClass* m_pNextClass;       // 链表结构,记录相关类的完整容器const AFX_CLASSINIT* m_pClassInit;// OperationsCObject* CreateObject();BOOL IsDerivedFrom(const CRuntimeClass* pBaseClass) const;// dynamic name lookup and creationstatic CRuntimeClass* PASCAL FromName(LPCSTR lpszClassName);static CObject* PASCAL CreateObject(LPCSTR lpszClassName);
};

链表的头节点与树的根节点都是“特殊”节点,需要特殊处理,在MFC框架中CObject就是对应的特殊类。

实现
运行时识别
MFC的运行时识别与DECLARE_DYNAMIC及IMPLEMENT_DYNAMIC两个宏有关。以下是简化版:

//放到类声明中,一个类对应一个全局的CRuntimeClass
#define DECLARE_DYNAMIC(class_name)
public:
static const CRuntimeClass class##class_name;
virtual CRuntimeClass* GetRuntimeClass() const; \

//作为全局(静态)数据,初始化
#define IMPLEMENT_DYNAMIC(class_name, base_class_name)
const CRuntimeClass class_name::class##class_name = {
#class_name, sizeof(class class_name), 0xFFFF, NULL,
RUNTIME_CLASS(base_class_name), NULL, NULL };
CRuntimeClass* class_name::GetRuntimeClass() const
{ return RUNTIME_CLASS(class_name); }
RUNTIME_CLASS也是一个宏,是为了更方便的得到静态数据CRuntimeClass:

//这个宏是为了更方便的获取类里面的静态数据成员
//如RUNTIME_CLASS(CView) 等价于 ((CRuntimeClass*)(&CView::classCView))

#define RUNTIME_CLASS(class_name) ((CRuntimeClass*)(&class_name::class##class_name))
建立了单向树之后,在CObject中添加成员IsKindOf(),即可通过遍历单向树来判断继承关系:

BOOL CObject::IsKindOf(CRuntimeClass *pClass)
{
CRuntimeClass *pClassThis = GetRuntimeClass();
while(pClassThis != NULL && pClassThis != pClass)
{
pClassThis = pClassThis->m_pBaseClass;
}

if (pClassThis == NULL)
{return FALSE;
}return TRUE;

}
动态创建
简单而言,动态创建的最终目的,就是能够通过字符串来创建对象。这可以在RTTI的基础上附带一个创造对象的接口实现:

#define DECLARE_DYNCREATE(class_name)
DECLARE_DYNAMIC(class_name)
static CObject* PASCAL CreateObject();
实现:

#define IMPLEMENT_DYNCREATE(class_name, base_class_name)
CObject* PASCAL class_name::CreateObject()
{ return new class_name; }
IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, 0xFFFF,
class_name::CreateObject, NULL)
#define IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, wSchema, pfnNew, class_init)
const CRuntimeClass class_name::class##class_name = {
#class_name, sizeof(class class_name), wSchema, pfnNew,
RUNTIME_CLASS(base_class_name), NULL, class_init };
CRuntimeClass* class_name::GetRuntimeClass() const
{ return RUNTIME_CLASS(class_name); }
以上宏为每一个支持动态创建的类(而不是CRuntimeClass)多声明、实现了一个静态CreateObject()接口,用于获取一个新对象。并且将CreateObject()初始化至对应的CRuntimeClass中。

每一个CRuntimeClass对应一个类,我们也就知道CRuntimeClass中的CreatObject()如何实现了:

CObject * CRuntimeClass::CreateObject()
{
//…各检查
return (*m_pfnCreateObject)();
}
最终,我们只需要一个函数,它的输入参数为字符串,其实现中去遍历CRuntimeClass链表并作判断,当类型一致时就调用CRuntimeClass对象的CreateObject(),就实现了动态创建。这就是CRuntimeClass中的静态成员函数所做的事。

struct CRuntimeClass
{
//…
static CObject* PASCAL CreateObject(LPCSTR lpszClassName);
/* 实现:

  • 从CRuntimeClass链表的首节点开始遍历(pClassThis = pFirst)
  • 比较lpszClassName与pClassThis->m_lpszClassName
  • 若相等,则调用并返回pClassThis->CreateObject()
    */
    };

技术交流群,群内分享视频、就业资源:C++大学技术协会:145655849

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/504071.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

更新elementui图标不显示_elementUI字体图标不显示问题

自己搭建的Vue项目,没有使用vue-cli,引入elementUI时提示字体图标404,找不到文件,如下错误:GET http://localhost:9090/WEB-INF/content/2fad952a20fbbcfd1bf2ebb210dccf7a.woff 404 (Not Found)在网上查了下解决办法&…

qt 3d迷宫游戏_机械迷宫—一款机械风格的3D立体解谜独立游戏

解谜游戏一直是游戏类别中的一个大类,这里面各式各样的解密游戏,多不胜数。解密游戏又分很多种类,比如动作冒险新式的解密游戏,文字图像类型的解密游戏;游戏制作上有大有小,但是都有一个共同特点&#xff0…

Ubuntu下的Linux内核的编译及安装

推荐群:C/C大学技术协会:145655849 Linux中的模块(Modules) Linux的module其实可以看作是内核的插件。 在Linux系统中,可以通过文件 cat /proc/modules xxxxxxxxxx1 1cat /proc/modules 查看相关的驱动模块。 也可以通过命令 l…

执行公式_一学就会,一吃就瘦,超简单又好执行的减肥食谱公式!

对于很多人来说,没有精确计算食物重量的条件,无法每天花费大量的时间精力去自己准备健康的食物,给大家一个不需要食物称,只要在日常饮食基础上看看自己的手掌就能通过类比来选择好食物的种类和合适比例的方法!碳水化合…

apollo源码分析 感知_Kitty中的动态线程池支持Nacos,Apollo多配置中心了

目录回顾昨日nacos 集成Spring Cloud Alibaba 方式Nacos Spring Boot 方式Apollo 集成自研配置中心对接无配置中心对接实现源码分析兼容 Apollo 和 Nacos NoClassDefFoundErrorApollo 自动刷新问题回顾昨日上篇文章 《一时技痒,撸了个动态线程池,源码放 …

如何使用Python操作MySQL数据库

安装引入模块 安装mysql模块 sudo apt-get install python-mysql 在文件中引入模块 import Mysqldb Connection对象 用于建立与数据库的连接 创建对象:调用connect()方法 connconnect(参数列表) 参数host:连接的mysql主机,如果本机是’loca…

c语言 把字符串转换为变量名_如何将抓取下来的unicode字符串转换为中文

如果抓取的数据是json数据,那么直接将抓取的数据用json格式输出出来就行了。如下:response requests.get(url, headersself.headers).json()如果是unicode字符串,那么请继续往下看大家有没有遇见抓取下来的数据是unicode字符串的?如下图所示…

完全弄懂C++中的构造与析构函数

类的构造函数 带参数的构造函数 使用初始化列表来初始化字段 类的析构函数 构造函数与析构函数的特点 显式调用析构函数 拷贝构造函数 类的构造函数 类的构造函数是类的一种特殊的成员函数,它会在每次创建类的新对象时执行。 构造函数的名称与类的名称是完全相同的&…

c++ 读取访问权限冲突_Linux系统利用可执行文件的Capabilities实现权限提升

一、操作目的和应用场景Capabilities机制是在Linux内核2.2之后引入的,原理很简单,就是将之前与超级用户root(UID0)关联的特权细分为不同的功能组,Capabilites作为线程(Linux并不真正区分进程和线程)的属性存在,每个功能组都可以独…

Python 数据分析 Matplotlib篇 时间序列数据绘制折线图(第4讲)

Python 数据分析 Matplotlib篇 时间序列数据绘制折线图(第4讲)         🍹博主 侯小啾 感谢您的支持与信赖。☀️ 🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹…

家装强电弱电布线图_家装水电施工标准(图文版),装修小白一眼也能看懂。...

如果把家比喻成一个人,房子是骨骼,那么水电路则相当于人体的血管和动脉,正因为他们的存在,才赋予家鲜活的生命。由此,水电施工自然也成为家装工程的重中之重。水电走线原则※ 水电弹线放样施工,使用切割机开…

0基础必看:如何轻松成为C语言高手

诞生于上世纪70年代的C语言是一门古老的语言了, 但作为一门底层语言,时至今日它仍然非常强大。学习C语言能够为学习其他更复杂的语言打下良好的基础,因为你在C语言中学习到的知识对几乎所有的编程语言都适用。下面就来看看如何开始用C语言编程吧。   工具   Mic…

python列表元素之和_python实现计算列表元素之和

目标:定义一个数字列表,并计算列表元素之和。 例如: 输入 : [12, 15, 3, 10] 输出 : 40 方法一:total 0 list1 [11, 5, 17, 18, 23] for ele in range(0, len(list1)): total total list1[ele] print("列表元素之和为: &…

双水泵轮换工作原理图_一用一备式冷凝水泵应急电源的设计与实现

为保障山西通州集团兴益化工有限公司10万吨甲醇工程一用一备式冷凝水泵的安全运行,专门设计动力负载应急电源。作者阐述了动力负载应急电源基本原理及其控制系统设计,经现场调试运行,动力负载应急电源很好满足生产现场要求。山西通州集团兴益…

基础学习——C语言递归解决分鱼问题

如有小伙伴想学习C语言基础,可以进群731871503进行交流学习,提升编程,共同进步 问题描述 A、B、C、D、E这5个人合伙夜间捕鱼,凌晨时都已经疲惫不堪,于是各自在河边的树丛中找地方睡着了。第二天日上三竿时&#xff0…

python定位元素在列表中的位置_python定位列表元素

Python 列表(list)提供了 index() 和 count() 方法,它们都可以用来查找元素。 index() 方法 index() 方法用来查找某个元素在列表中出现的位置(也就是索引),如果该元素不存在,则会导致 ValueErr…

Java学习4大阶段完成入门,小白必读!

Java 的学习过程分为4个阶段: 理论阶段 开发阶段 进阶阶段 实战阶段 理论阶段 在具体谈论Java知识点之前,我想先跟同学们聊聊Java 语言自身的一些特点、生态系统以及适用的场景,这有助于我们更好的学习语言本身。 首先,我们…

memkind版本查看_不同价位值得买轻薄本推荐~2020国庆篇

说起轻薄本,你理想中的一台优秀机型是什么样的?轻薄便携、质感上乘、高颜值、逼格、手感佳、续航持久、屏幕素质高、独立小键盘、性能激进……和游戏本的鲜明对比,注定了是两种不同的“菜”。2020年,随着AMD的崛起,部分…

main方法 如何去掉http debug日志_在MyBatis中如何使用collection标签实现嵌套查询?...

# 需求升级在上篇博客《一对多的关系,在MyBatis中如何映射?》中,我们实现了需求:根据用户id查询用户信息的同时获取用户拥有的角色。因为角色可以拥有多个权限,所以本篇博客我们升级需求为:根据用户id查询用…

movielens推荐系统_案例|推荐系统的评估指标

推荐系统能够为用户提供个性化体验,现在基本上各大电商平台、资讯平台都会用推荐系统为自家评价下的用户提供千人千面的服务。平均精度均值(Mean Average Precision,MAP)便是评估推荐系统性能的度量标准之一。但是,使用…