MFC之RTTI与动态创建

时间:2024-12-27 20:37:21

本人能力、精力有限,所言所感都基于自身的实践和有限的阅读、查阅,如有错误,欢迎拍砖,敬请赐教——博客园:钱智慧。

在说RTTI之前需要明白c++中类静态成员的初始化特点:类的静态数据成员需要在类体外显式初始化(const 类型的静态数据成员才能在类体内直接初始化)。关于数据(全局变量,局部变量,静态变量,类数据成员)的初始化特点可参考《C++primer》相关章节。

还要知道,类的静态成员的初始化发生于main之前,MFC的RTTI机制正是利用了这一特点,从而在进入main函数之前提前把一个信息链表建立起来,而这个链表每个节点便是一个CruntimeClass对象。那么,在main之前就得把链表构建好,这意味着在main之前就得执行代码,试问:什么代码能在main之前得以执行呢?这便是全局对象或者静态对象的构造函数中的代码。比如,有个全局对象Person p;那么,p的造函数会在main执行之前执行,从而我们可以把一些提前完成的任务放在Person的构造函数里,Person本身甚至完全不重要,我们只是想利用它的构造函数来完成自己想要的功能而已,MFC的RTTI也利用了这一点,比如其中的AFX_CLASSINIT类(结构体),它存在的唯一目的就是为了利用其构造函数往链表中添加节点,再比如接下来的模拟例子中的AFX_ClassNode类也是如此。下面的例子仅仅是模拟了RTTI中构建链表的过程,并不是完整的RTTI机制模拟,没有候捷的模拟复杂,所以更容易看懂,代码如下:

 #include <iostream>

 //链表节点类
struct ClassNode
{
ClassNode* next;
//注意,节点类有个静态指针,因为一个链表只需要有一个首指针
static ClassNode* first;//first本身是Node类的静态数据成员,需要在类体外初始化
};
ClassNode* ClassNode::first=;
//继承体系
/* Fruit
|
-----------
| |
Orange Apple
|
BigApple
*/
struct AFX_LISTINIT//这个类的存在只是为了利用其构造函数来给链表增加节点
{
AFX_LISTINIT(ClassNode* pNode)
{
printf("添加一个节点构建链表\n");
pNode->next=ClassNode::first;
ClassNode::first=pNode;
}
};
//Fruit
class Fruit
{
public:
static ClassNode node;
};
//下面两行代码把Fruit的静态数据成员node节点加入链表(此时链表一个节点都没有)
ClassNode Fruit::node={};//Fruit类的指针初始化为0,就是null
AFX_LISTINIT initFruit(&Fruit::node);
//Orange
class Orange:public Fruit
{
public:
static ClassNode node;
};
ClassNode Orange::node={};//Node是结构体,所以可以这样初始化
AFX_LISTINIT initOrange(&Orange::node);
//Apple
class Apple:public Fruit
{
public:
static ClassNode node;
};
ClassNode Apple::node={};
AFX_LISTINIT initApple(&Apple::node);
//BigApple
class BigApple:public Apple
{
public:
static ClassNode node;
};
ClassNode BigApple::node={};
AFX_LISTINIT initBigApple(&BigApple::node); int main()
{
return ;
}

最终可以看到有四句打印语句。

如同MFC中RTTI中的链表一样,本例的链表也是倒着添加节点的,即先添加的节点将会在链表靠后的位置。

引入宏定义:

我们的模拟很简陋,仅仅是模拟出了创建链表的机制,但是这个链表是粗糙的,每个节点几乎没有保存跟特定类相关的信息,比如类名,比如指向基类节点的指针,比如用来创建类实例的函数指针等。而且我们每定义一个新的类,如果想加入链表都需要两句类似的代码:初始化节点,构建一个AFX对象以添加节点,这无疑体力和键盘的消磨,真正的RTTI中用宏避免了手动敲击重复代码,宏并不神秘,代码替换而已。我们一步步完善这个模拟。我们的链表中节点类型是ClassNode,MFC中是CRuntmeClass类型(结构体),而且MFC中每个类的静态数据成员CRuntimeClass对象有不同的名称,格式是:class+类名,比如classOrange,而我们的类中的节点变量名称都叫node,我们就来改进这一点,让每个的节点变量名具备class+类名的格式,利用C语言一种特殊的预处理器语法 a##b得到ab,注意,a和b可以是任意类型的变量名称,通过这种方式可以得到一种新的变量名ab,从而我们的改进方式是:直接将使用node变量名的地方换成class##Fruit,class##Apple,class##Orange……然而这是不行的,因为##语法是预处理器负责的,VC6.0给的错误是:preprocessor command must start as first nonwhite space。这就是在逼着我们用宏定义了,最终改善的代码:

 #include <iostream>

 #define DECLARE_DYNAMIC(class_name) \
public: \
static ClassNode class##class_name; #define IMPLEMENT_DYNAMIC(class_name) \
ClassNode class_name::class##class_name={}; \
AFX_LISTINIT init##class_name(&class_name::class##class_name); //链表节点类
struct ClassNode
{
ClassNode* next;
static ClassNode* first;
};
ClassNode* ClassNode::first=; struct AFX_LISTINIT
{
AFX_LISTINIT(ClassNode* pNode)
{
printf("构建链表:添加一个节点\n");
pNode->next=ClassNode::first;
ClassNode::first=pNode;
}
};
//Fruit
class Fruit
{
DECLARE_DYNAMIC(Fruit)
};
IMPLEMENT_DYNAMIC(Fruit); //Orange
class Orange:public Fruit
{
DECLARE_DYNAMIC(Orange)
};
IMPLEMENT_DYNAMIC(Orange) //Apple
class Apple:public Fruit
{
DECLARE_DYNAMIC(Apple)
};
IMPLEMENT_DYNAMIC(Apple)
//BigApple
class BigApple:public Apple
{
DECLARE_DYNAMIC(BigApple)
};
IMPLEMENT_DYNAMIC(BigApple) int main()
{
return ;
}

引入类名:

 #include <iostream>

 //注意宏定义中的#和##语法,前者可把变量名转成字符串

 #define DECLARE_DYNAMIC(class_name) \
public: \
static ClassNode class##class_name; #define IMPLEMENT_DYNAMIC(class_name) \
char _sz##class_name[]=#class_name; \
ClassNode class_name::class##class_name={_sz##class_name,}; \
AFX_LISTINIT init##class_name(&class_name::class##class_name); //链表节点类
struct ClassNode
{
char* m_szClassName;
ClassNode* next;
static ClassNode* first;
};
ClassNode* ClassNode::first=; struct AFX_LISTINIT
{
AFX_LISTINIT(ClassNode* pNode)
{
printf("构建链表:添加一个节点:");
printf(pNode->m_szClassName);
printf("\n");
pNode->next=ClassNode::first;
ClassNode::first=pNode;
}
};
//Fruit
class Fruit
{
DECLARE_DYNAMIC(Fruit)
};
IMPLEMENT_DYNAMIC(Fruit); //Orange
class Orange:public Fruit
{
DECLARE_DYNAMIC(Orange)
};
IMPLEMENT_DYNAMIC(Orange) //Apple
class Apple:public Fruit
{
DECLARE_DYNAMIC(Apple)
};
IMPLEMENT_DYNAMIC(Apple)
//BigApple
class BigApple:public Apple
{
DECLARE_DYNAMIC(BigApple)
};
IMPLEMENT_DYNAMIC(BigApple) int main()
{
return ;
}

引入继承关系:

引入继承关系,便是在节点类中增加一个指向父类结点的指针,问题是如何初始这个指针呢,这要求我们必须提供获得某个类的节点指针的方法,从而我们需要定义RUNTIME_CLASS宏,它接收类名,返回类的节点指针。还要注意,终极父类Fruit并没有父类,所以不能像其他类那样对其直接使用实现宏,而是要特殊处理,即把其父类置空(毕竟RUNTIME_CLASS宏不能接收NULL),事实上RUNTIME_CLASS宏与即将实现的GetRuntimClass成员函数功能是一样的,只不过一个是宏,一个是成员函数,只能通过类对象调用。最终代码如下:

 #include <iostream>

 //注意宏定义中的#和##语法,前者可把变量名转成字符串
#define RUNTIME_CLASS(class_name) (&class_name::class##class_name)
#define DECLARE_DYNAMIC(class_name) \
public: \
static ClassNode class##class_name; \
ClassNode* GetRuntimeClass(); #define IMPLEMENT_DYNAMIC(class_name,base_class_name) \
char _sz##class_name[]=#class_name; \
ClassNode class_name::class##class_name={RUNTIME_CLASS(base_class_name),_sz##class_name,}; \
AFX_LISTINIT init##class_name(&class_name::class##class_name); \
ClassNode* class_name::GetRuntimeClass(){return &class_name::class##class_name;} //链表节点类
struct ClassNode
{
ClassNode* m_baseClass;
char* m_szClassName;
ClassNode* next;
static ClassNode* first;
};
ClassNode* ClassNode::first=; struct AFX_LISTINIT
{
AFX_LISTINIT(ClassNode* pNode)
{
printf("构建链表:添加一个节点:");
printf(pNode->m_szClassName);
printf("\n");
pNode->next=ClassNode::first;
ClassNode::first=pNode;
}
};
//Fruit
class Fruit
{
DECLARE_DYNAMIC(Fruit)
};
//IMPLEMENT_DYNAMIC(Fruit);特殊处理最终父类,MFC中便是上帝CObject
char _szFruit[]="Fruit";
ClassNode Fruit::classFruit={/*RUNTIME_CLASS(base_class_name)*/NULL,_szFruit,};
AFX_LISTINIT initFruit(&Fruit::classFruit); //Orange
class Orange:public Fruit
{
DECLARE_DYNAMIC(Orange)
};
IMPLEMENT_DYNAMIC(Orange,Fruit) //Apple
class Apple:public Fruit
{
DECLARE_DYNAMIC(Apple)
};
IMPLEMENT_DYNAMIC(Apple,Fruit)
//BigApple
class BigApple:public Apple
{
DECLARE_DYNAMIC(BigApple)
};
IMPLEMENT_DYNAMIC(BigApple,Apple) int main()
{
printf("***********************\n");
//**测试链表中基类信息
//打印出各个类及父类名称
for(ClassNode* cursor=ClassNode::first;cursor!=NULL;)
{
ClassNode * baseClass=cursor->m_baseClass;
if(baseClass!=NULL)
{
printf("%s : %s\n",cursor->m_szClassName,baseClass->m_szClassName);
}
else
{
printf(cursor->m_szClassName);
printf("\n");
}
cursor=cursor->next;
}
printf("***********************\n");
//**用GetRumtimeClass方法测试
//找出ba所属类型的所有祖宗
BigApple ba;
for(ClassNode* findBase=ba.GetRuntimeClass()->m_baseClass;findBase!=NULL;)
{
printf(findBase->m_szClassName);
printf("\n");
findBase=findBase->m_baseClass;
} return ;
}

引入类型识别函数IsKindOf函数:

把IsKindOf定义在根父类Fruit中,这样后代类都能继承到。IsKindOf是一个普通的成员函数,其参数是一个ClassNode指针(MFC中则是CRuntimeClass指针),它的思想很简单:先用本类的ClassNode指针与参数比较,若不相等再用本类的父类的ClassNode指针去比较……一旦相等,便返回真。比较两指针的值是完全没问题的,我们知道,若两指针的值相等,便能说明二者指向同一个对象,此处若两指针相等则说明二者指向同一个ClassNode对象。

尤其要注意的是:之前我们的GetRuntimeClass对象不是虚函数,但到这一步,它必须得是虚函数,因为IsKindOf调用了GetRuntimeClass函数,看代码:

BigApple ba;

printf("%d\n",ba.isKindOf(RUNTIME_CLASS(Apple)));

若没用虚函数,则执行到isKindOf中时,不能做到动态绑定而调用ba自己的GetRuntimeClass函数,从而出现逻辑错误。完整代码:

 #include <iostream>

 //注意宏定义中的#和##语法,前者可把变量名转成字符串
#define RUNTIME_CLASS(class_name) (&class_name::class##class_name)
#define DECLARE_DYNAMIC(class_name) \
public: \
static ClassNode class##class_name; \
virtual ClassNode* GetRuntimeClass();//这里必须是虚函数!! #define IMPLEMENT_DYNAMIC(class_name,base_class_name) \
char _sz##class_name[]=#class_name; \
ClassNode class_name::class##class_name={RUNTIME_CLASS(base_class_name),_sz##class_name,}; \
AFX_LISTINIT init##class_name(&class_name::class##class_name); \
ClassNode* class_name::GetRuntimeClass(){return &class_name::class##class_name;} //链表节点类
struct ClassNode
{
ClassNode* m_baseClass;
char* m_szClassName;
ClassNode* next;
static ClassNode* first;
};
ClassNode* ClassNode::first=; struct AFX_LISTINIT
{
AFX_LISTINIT(ClassNode* pNode)
{
printf("构建链表:添加一个节点:");
printf(pNode->m_szClassName);
printf("\n");
pNode->next=ClassNode::first;
ClassNode::first=pNode;
}
}; //Fruit
class Fruit
{
public:
bool isKindOf(ClassNode* pNode);
DECLARE_DYNAMIC(Fruit)
};
//IMPLEMENT_DYNAMIC(Fruit);特殊处理最终父类,MFC中便是上帝CObject
char _szFruit[]="Fruit";
ClassNode Fruit::classFruit={/*RUNTIME_CLASS(base_class_name)*/NULL,_szFruit,};
AFX_LISTINIT initFruit(&Fruit::classFruit);
ClassNode* Fruit::GetRuntimeClass(){return &Fruit::classFruit;}
bool Fruit::isKindOf(ClassNode* pNode)
{
for(ClassNode* cursor=this->GetRuntimeClass();cursor!=NULL;)
{
if(cursor==pNode)
{
return true;
}
cursor=cursor->m_baseClass;
}
return false;
} //Orange
class Orange:public Fruit
{
DECLARE_DYNAMIC(Orange)
};
IMPLEMENT_DYNAMIC(Orange,Fruit) //Apple
class Apple:public Fruit
{
DECLARE_DYNAMIC(Apple)
};
IMPLEMENT_DYNAMIC(Apple,Fruit)
//BigApple
class BigApple:public Apple
{
DECLARE_DYNAMIC(BigApple)
};
IMPLEMENT_DYNAMIC(BigApple,Apple) int main()
{
BigApple ba;
//**测试isKindOf(此处最值得注意的地方便是Fruit类的GetRuntimeClass必须得是虚函数)
printf("%d\n",ba.isKindOf(RUNTIME_CLASS(Apple)));
printf("%d\n",ba.isKindOf(RUNTIME_CLASS(Orange))); return ;
}

另外,本示例代码的全局变量不像候捷的示例代码那样让全局变量都为静态的,这并无大碍,但要明白全局的静态变量只能在本文件中使用,虽然其生命周期是整个程序的运行期,非静态的全局变量才能通过extern声明被外部文件使用,全局函数也是这样。

引入动态创建:

思想就是为每个类加入一个静态的createObject函数,为ClassNode节点加入一个函数指针成员,构建链表时中每个节点时,把指针指向每个类自己的createObject函数,从而将来有了相应类的节点指针(即CRuntimClass指针)便能调用相应的createObject方法。需要注意的是,我们也为ClassNode引入一个普通的成员方法CreateObject,这个方法是将来动态创建时真正要调用的方法,它的存在不过是让功能更优雅一些,其实没有它也可以,不过调用者需要自己判断ClassNode对象里的函数指针是不是为空,不为空才能调用该指针指向的类的静态createObject方法。至今,为了省事,我们的根级父类Fruit定义中一直还有动态声明宏,但这里要把它拿掉而用具体的代码了,因为我们不让Fruit具备动态创建功能,即不给其添加createObject方法,其节点中的m_pfnCreateObject指针也置空。另外为了测试方便,我们给根父类Fruit加入了虚方法sayHello,子类都覆写了这个方法,候捷的的动态创建示例代码中有Load方法,其实它完成的便是本例的main函数代码,候哥这么做是为了契合后续的持久化机制。完整代码:

 #include <iostream>

 //注意宏定义中的#和##语法,前者可把变量名转成字符串
#define RUNTIME_CLASS(class_name) (&class_name::class##class_name)
#define DECLARE_DYNAMIC(class_name) \
public: \
static ClassNode class##class_name; \
virtual ClassNode* GetRuntimeClass(); \
static Fruit* createObject(); #define IMPLEMENT_DYNAMIC(class_name,base_class_name) \
char _sz##class_name[]=#class_name; \
ClassNode class_name::class##class_name={class_name::createObject,\
RUNTIME_CLASS(base_class_name),_sz##class_name,}; \
AFX_LISTINIT init##class_name(&class_name::class##class_name); \
ClassNode* class_name::GetRuntimeClass(){return &class_name::class##class_name;} \
Fruit* class_name::createObject() {return new class_name;} //**链表节点类
//提前用到了Fruit类,需要前向声明
class Fruit;
struct ClassNode
{
Fruit* CreateObject();
Fruit* (*m_pfnCreateObject)();
ClassNode* m_baseClass;
char* m_szClassName;
ClassNode* next;
static ClassNode* first;
};
Fruit* ClassNode::CreateObject()
{
if(m_pfnCreateObject==NULL)
{
printf("这个类不支持动态创建\n");
return NULL;
}
return m_pfnCreateObject();
}
ClassNode* ClassNode::first=; struct AFX_LISTINIT
{
AFX_LISTINIT(ClassNode* pNode)
{
printf("构建链表:添加一个节点:");
printf(pNode->m_szClassName);
printf("\n");
pNode->next=ClassNode::first;
ClassNode::first=pNode;
}
}; //Fruit
class Fruit
{
public:
virtual void sayHello(){printf("hello Fruit\n");}
bool isKindOf(ClassNode* pNode);
static ClassNode classFruit;
virtual ClassNode* GetRuntimeClass();
};
//IMPLEMENT_DYNAMIC(Fruit);特殊处理最终父类,MFC中便是上帝CObject
char _szFruit[]="Fruit";
ClassNode Fruit::classFruit={NULL,/*RUNTIME_CLASS(base_class_name)*/NULL,_szFruit,};
AFX_LISTINIT initFruit(&Fruit::classFruit);
ClassNode* Fruit::GetRuntimeClass(){return &Fruit::classFruit;}
bool Fruit::isKindOf(ClassNode* pNode)
{
for(ClassNode* cursor=this->GetRuntimeClass();cursor!=NULL;)
{
if(cursor==pNode)
{
return true;
}
cursor=cursor->m_baseClass;
}
return false;
} //Orange
class Orange:public Fruit
{
public:
void sayHello(){printf("hello Orange\n");}
DECLARE_DYNAMIC(Orange)
};
IMPLEMENT_DYNAMIC(Orange,Fruit) //Apple
class Apple:public Fruit
{
public:
void sayHello(){printf("hello Apple\n");}
DECLARE_DYNAMIC(Apple)
};
IMPLEMENT_DYNAMIC(Apple,Fruit)
//BigApple
class BigApple:public Apple
{
public:
void sayHello(){printf("hello BigApple\n");}
DECLARE_DYNAMIC(BigApple)
};
IMPLEMENT_DYNAMIC(BigApple,Apple) int main()
{
//**动态创建测试
char className[];
scanf("%s",&className);
for(ClassNode* cursor=ClassNode::first;cursor!=NULL;)
{
if(strcmp(cursor->m_szClassName,className)==)
{
cursor->CreateObject()->sayHello();
break;
}
cursor=cursor->next;
}
return ;
}

到此,本示例算是完全结束了,为了简单省事,本示例对候哥的例子做了尽可能的简化。