假设需要一个类别库,改类别库共包含以下5个类:GrandFather(祖父类)、Father(父类)、Son(儿子类)、Daughter(女儿类)、GrandSon(孙子类)

各个类之间的继承关系为:

相应的代码为:

class GrandFather
{
}; class Father:public GrandFather
{
}; class Son:public Father
{
}; class Daughter:public Father
{
}; class GrandSon:public Son
{
};

想要让该类别库具备IsKindOf的功能,即能在执行时期侦测某个对象是否“属于某个类别”,并传回TRUE或FALSE。

希望实现如下效果:

GrandFather* pGrandSon = new GrandSon;
cout << pGrandSon->IsKindof(GrandFather); //应该输出1
cout << pGrandSon->IsKindof(Father); //应该输出1
cout << pGrandSon->IsKindof(Son); //应该输出1
cout << pGrandSon->IsKindof(Daughter); //应该输出0

设计思路:

pGrandSon->IsKindof(GrandFather)

为例,想要在运行时判断:GrandSon 类是否是 GrandFather的子类。

我们知道,GrandSon类的继承路线是这样的:GrandSon--->Son--->Father--->GrandFather,因此GrandSon是GrandFather的子类。

所以我们在进行判断的时候,需要从GrandSon类的父类进行找起,然后一级级的往上找,然后判断某个父类是否为:GrandFather。

换句话说,想要判断GrandSon是否是GrandFather的子类,需要“有迹可循”。

以上的“迹”,我们可以通过一个结构体:CRuntimeClass,来进行保存。

该结构体至少需要保存如下信息:类别名称、串行的Next 指针,以及串行的First 指针。

First指针属于全域变量,一份就好,所以应该是static变量。

struct CRuntimeClass
{
LPCSTR m_lpszClassName;
int m_nObjectSize;
UINT m_wSchema;
GrandFather* (PASCAL* m_pfnCreateObject)();
CRuntimeClass* m_pBaseClass;
static CRuntimeClass* pFirstClass;
CRuntimeClass* m_pNextClass;
};

要实现IsKindOf()的功能,则类别库中的5个类(GrandFather、Father、Son、Daughter、GrandSon)都应该各拥有一个CRuntimeClass成员变量。

进一步将类修改如下:

struct CRuntimeClass
{
LPCSTR m_lpszClassName;
static CRuntimeClass* pFirstClass;
CRuntimeClass* m_pNextClass;
CRuntimeClass* m_pBaseClass;
}; class GrandFather
{
public:
static CRuntimeClass classGrandFather;
virtual CRuntimeClass* GetRuntimeClass() const{return &GrandFather::classGrandFather;}
}; class Father:public GrandFather
{
public:
static CRuntimeClass classFather;
virtual CRuntimeClass* GetRuntimeClass() const{ return &Father::classFather;}
}; class Son:public Father
{
public:
static CRuntimeClass classSon;
virtual CRuntimeClass* GetRuntimeClass() const { return &Son::classSon; }
}; class Daughter:public Father
{
public:
static CRuntimeClass classDaughter;
virtual CRuntimeClass* GetRuntimeClass() const { return &Daughter::classDaughter; }
}; class GrandSon:public Son
{
public:
static CRuntimeClass classGrandSon;
virtual CRuntimeClass* GetRuntimeClass() const { return &GrandSon::classGrandSon; }
};

其中,GetRuntimeClass为内联虚函数,作用为:获取当前对象所属类的 「成员变量类型为CRuntimeClass」的成员变量地址。

如:

CRuntimeClass* GrandSon::GetRuntimeClass() const
{
return &GrandSon::classGrandSon;
}

返回成员变量classGrandSon的内存地址

接下来,需要完成最重要的一步:填充各个类中CRuntimeClass结构体的数据内容。

//祖父
static char szGrandFather[] = "GrandFather";
CRuntimeClass GrandFather::classGrandFather = { szGrandFather}; //父亲
static char _lpszFather[] = "Father";
CRuntimeClass Father::classFather = {_lpszFather, NULL ,&GrandFather::classGrandFather}; //儿子
static char _lpszSon[] = "Son";
CRuntimeClass Son::classSon = {_lpszSon,NULL ,&Father::classFather }; //女儿
static char _lpszDaughter[] = "Daughter";
CRuntimeClass Daughter::classDaughter = {_lpszDaughter,NULL ,&Father::classFather}; //孙子
static char _lpszGrandSon[] = "GrandSon";
CRuntimeClass GrandSon::classGrandSon = {_lpszGrandSon, NULL ,&Son::classSon};

ClassFile.cpp

以上代码将填充CRuntimeClass结构体的

LPCSTR m_lpszClassName(类名)、CRuntimeClass* m_pBaseClass(基类的CRuntimeClass结构地址)。

另外,还有2个成员变量需要进行初始化:pFirstClass和m_pNextClass

pFirstClass是静态的,需要初始化为NULL:CRuntimeClass* CRuntimeClass::pFirstClass = NULL;

m_pNextClass的初始化操作,可以通过以下的结构体构造函数来完成:

在ClassFile.h头文件中,增加如下代码:

struct AFX_CLASSINIT
{
AFX_CLASSINIT(CRuntimeClass* pNewClass)
{
pNewClass->m_pNextClass = CRuntimeClass::pFirstClass;
CRuntimeClass::pFirstClass = pNewClass;
}
};

以上代码定义了一个结构体:AFX_CLASSINIT,并在结构体的构造函数中(C++中结构体也是有构造函数的……)对m_pNextClass 和 pFirstClass进行赋值。

在通初始化结构体的其他成员变量一样,需要我们手动在ClassFile.cpp文件中添加初始化另外几个成员变量的代码:

//祖父
static AFX_CLASSINIT _init_GrandFather(&GrandFather::classGrandFather);
//父亲
static AFX_CLASSINIT _init_Father(&Father::classFather);
//儿子
static AFX_CLASSINIT _init_Son(&Son::classSon);
//女儿
static AFX_CLASSINIT _init_Daughter(&Daughter::classDaughter);
//孙子
static AFX_CLASSINIT _init_GrandSon(&GrandSon::classGrandSon);

当执行以上代码的时候,将把各个类中的CRuntimeClass结构体串联起来。

这样,经过以上的步骤,整个类别库表就构造好了!类似下图所示:

有了以上的类库表之后,实现IsKindOf()函数的功能就轻而易举了:

BOOL GrandFather::IsKindof(const CRuntimeClass* pClass)const
{
CRuntimeClass* pClassThis = GetRuntimeClass();
while(pClassThis != NULL)
{
if (pClassThis == pClass)
return TRUE;
pClassThis = pClassThis->m_pBaseClass;
}
return FALSE;
}

接着,就可以在main函数中进行调用了:

void main()
{
GrandFather* pGrandSon = new GrandSon;
cout << "pGrandSon->IsKindOf(RUNTIME_CLASS(GrandFather)) "<< pGrandSon->IsKindof(&GrandFather::classGrandFather)<< "\n";
cout << "pGrandSon->IsKindOf(RUNTIME_CLASS(Father)) "<< pGrandSon->IsKindof(&Father::classFather)<< "\n";
cout << "pGrandSon->IsKindOf(RUNTIME_CLASS(Son)) "<< pGrandSon->IsKindof(&Son::classSon) << "\n";
cout << "pGrandSon->IsKindOf(RUNTIME_CLASS(Daughter)) "<< pGrandSon->IsKindof(&Daughter::classDaughter) << "\n";
delete pGrandSon;
getchar();
}

执行结果如下:

参考资料:

《深入浅出MFC》

欢迎转载,但转载请著名出处:曾是土木人

C++:实现类似MFC的IsKindOf功能的更多相关文章

  1. MFC画线功能总结

    本文仅用于学习交流,商业用途请支持正版!转载请注明:http://www.cnblogs.com/mxbs/p/6216464.html MFC画线功能要点有二:其一,鼠标按下时记录初始位置为线的起始 ...

  2. 实现类似mysql group_concat的功能

    实现类似mysql group_concat的功能 SELECT SG.Id ,SG.GroupName ,HostNames = STUFF((SELECT ',' + SH.[HostName] ...

  3. 实现 Win32 程序的消息映射宏(类似 MFC )

    对于消息映射宏,不用多说了,用过 MFC 的人都很清楚.但目前有不少程序由于各种原因并没有使用 MFC,所以本帖讨论一下如何在 Win32 程序中实现类似MFC的消息映射宏.其实 Windows 的头 ...

  4. 利用原生JS实现类似浏览器查找高亮功能(转载)

    利用原生JS实现类似浏览器查找高亮功能 在完成 Navify 时,增加一个类似浏览器ctrl+f查找并该高亮的功能,在此进行一点总结: 需求 在.content中有许多.box,需要在.box中找出搜 ...

  5. WPF中类似使用tab键功能,可以向上向下定位

    原文:WPF中类似使用tab键功能,可以向上向下定位 private void tbYyrs_KeyUp(object sender, KeyEventArgs e) { UIElement elem ...

  6. mysql定时执行及延时执行,实现类似sql server waitfor功能

    熟悉SQL Server的人都知道,它有一个很有用的功能,waitfor time和waitfor delay,前者表示在某个时间执行,后者表示等待多长时间执行.在我们测试功能和定时执行的时候特别有用 ...

  7. 类似QQ侧滑菜单功能实现

    之前的那文章简单实现了菜单侧拉功能,但是做不到像QQ那样导航条和tabBar一起移动...之后在网上找资料,有了思路,就自个写了个demo试试水. 先创建QHLMainController控制器,并把 ...

  8. koa2 controller中实现类似sleep的延迟功能

    今天有同事问我如何在koa2中的controller中使用延迟执行的功能,他直接在controller中使用setTimeout,但是没效果. 错误的代码类似下面这样: // 错误的方法 export ...

  9. Kotlin 使用类似C# 的yield功能

    用过c#的可能对 yield 关键字爱不释手,那么在像我这种被迫上java贼船的人,就想找到类似的功能. 我使用的是kotlin,下面的方法演示了产生一个序列的功能. val fibonacciSeq ...

随机推荐

  1. SVN previous operation has not finished

    svn提交遇到恶心的问题,可能是因为上次cleanup中断后,进入死循环了. 错误如下: 解决方法:清空svn的队列 1.下载sqlite3.exe 2.找到你项目的.svn文件,查看是否存在wc.d ...

  2. cyclone IV中DDR的一个疑惑

    的生成的DDR2 IP中DDR的时钟竟然是双向的,而在arria10中生成的DDR4则是输出,而DDR2的datasheet上也指出ck和ck#是输入,不知为什么? inout mem_clk , i ...

  3. linux下修改root密码以及找回密码的方法

    以root身份登陆,执行: # passwd 用户名 (修改密码) # useradd 用户名 (添加用户) 具体示例如下:[root@bogon ~]# passwd root Changing p ...

  4. Vue的路由设置

    一.路由基础介绍 1.什么是前端路由? 路由是根据不同的url地址展示不同的内容或页面 前端路由就是把不同路由对应不同的内容或页面的任务交给前端来做,之前是通过服务器根据url的不同返回不同的页面实现 ...

  5. SRM476

    250pt 题意:饲养N<=50只Badgers,每只食量是X[i],当没看到别的一只Badgers吃东西时,它的食量就会增加Y[i],现在一共用P的粮食,问最多能养的起多少只獾. 思路:枚举一 ...

  6. windows安装mysql 5.7

    1.下载mysql 5.7 压缩包,解压在D:\software\mysql\目录下,更名称mysql-5.7.22 ,并新建data空文件夹和my.ini文件 my.ini文件的内容 [client ...

  7. Swift3 页面顶部实现拉伸效果代码

    //懒加载 //顶部需要拉伸自定义视图 lazy var headView:MyHeaderView = { //let hframe = CGRect(x: 0, y: 0, width: swid ...

  8. Memcached和Memcache安装(64位win2008)

    一.Memcached和Memcache的区别: 网上关于Memcached和Memcache的区别的理解众说纷纭,我个人的理解是: Memcached是一个内存缓存系统,而Memcache是php的 ...

  9. 受邀与微软售前技术团队参与TFS技术沟通会议

    微软正式发布Azure DevOps Server 2019的第一个版本,作为Team Foundation Server (TFS)2018的升级版本和替代产品. 作为开发运维一体化平台的主打产品, ...

  10. [翻译] ASP.NET WebAPI 中的异常处理

    原文链接:https://docs.microsoft.com/en-us/aspnet/web-api/overview/error-handling/exception-handling 本文介绍 ...