DECLARE_SERIAL / IMPLEMENT_SERIAL 宏的技术详解

来源:互联网 发布:在淘宝上的原创服饰 编辑:程序博客网 时间:2024/05/16 11:59
DECLARE_DYNAMIC表明的是支持类型信息, 有了这个宏,我们就可以判断一个类究竟是什么类,比如
class A;
class B:public A;
A a;
B b;
现在有一个指针 class *pA 它指向一个对象, 请问你怎么知道pA指向的是a对象还是b对象,这是如果有类型信息,我们就可以知道pA到底是什么对象, 其实,它内部的实现原理是一个字符串,所以,进行这个判断时,实际上是字符串比较.


DECLARE_DYNCREATE是动态创建的意思.这个有点类似Com的类工厂.
它实际上是用类CRunTime class记录了类的静态创建函数的地址.这个特性在很多地方需要使用.就在下面说的DECLARE_SERIAL就是一个经典的例子.
动态创建主要用在 "我不知道要创建的对象就是是什么类,但是我知道它肯定是从某个基类派生的".


DECLARE_SERIAL是指序列化特性,它是一个完全自动化的存储机制,它可以将一个对象数组(可能含有A,B,C类的对象)存储进去,而且能够根据存储的情况准确的载入进来,这看起来很简单, 但是,有一个问题我们必须考虑, 就是怎么写这个程序,使得载入的时候能够正确创建相应的A,B,C类的对象呢(注意,这里是三个不同的类).而且MFC的设计人员当初编写这个机制的时候根本不知道到底会出现什么类,也许还会出现D类. 怎么办呢? 
可以肯定,存储机制中必须要有能够判断类种类的代码.所以,序列化机制DECLARE_SERIAL包含了DECLARE_DYNAMIC,这样在存储进入文件的时候,可以将类名称存储到文件中.
OK,现在我们载入的时候可以知道我们要载入什么类了,但是,我们又要怎么去创建它呢? 所以DECLARE_SERIAL也包含了DECLARE_DYNCREATE,它用于创建对象.
那么,DECLARE_SERIAL到底有什么特殊的地方呢?首先,它必须实现operator>>(具体原因可以看看深入浅出,还有版本控制,这样,我们在处理序列化时,可以很灵活.




首先记住一点,DECLARE_SERIAL最主要的用途是一种智能存储.所以我们可以不用这个智能特性.
当我们没有DECLARE_SERIAL,而有void CMessg::Serialize(CArchive& ar)时,我们只能这样进行存储
CDocument::Serialize(ar)
{
     if (ar.isstoring())
    {
        //存储一个对象
        pMessg->Serialize(ar);
     }
     else
     {
        //必须非常明确的指出New一个 CMessg对象;
        pMessg = new CMessg;
        pMessg->Serialize(ar);
     }
}
在上面这个例子中,根本没有利用MFC为我们设计的序列化只能机制.


再看下面一个例子


CDocument::Serialize(ar)
{
     if (ar.isstoring())
    {
        //存储一个对象
        ar << pMessg;
     }
     else
     {
        //必须非常明确的指出New一个 CMessg对象;
        ASSERT(pMessg == NULL);
        ar >> pMessg;
     }
}


很神奇吧, ar是怎么根据文件(强调一下,是根据文件,而不是硬编码)判断需要创建什么类的.
它大概有这么几个步骤:
1. 因为DECLARE_SERIAL重载了>>操作符,所以可以保证是调用CMessg类的>>函数.
2. >>函数实际上调用的是ar的ReadObject(CRuntimeClass*)函数
3. ReadObject首先从文件中读取类判断信息(可能是一个字符串,可能是一个类索引),得到Class对应的ClassName;
4. 程序的模块状态中有所有的RuntimeClass的列表,因此,查找对应的程序支持的RuntimeClass(对比ClassName),获得对应的RuntimeClass;
5. RuntimeClass中含有创建对象的方法CreateObject,调用它,创建对应的对象.这里,因为CreateObject实际就是 New 一个对象,类似 new CMessg; 所以,为了支持序列化,必须有没有参数的构造函数.
6. 创建对象之后,调用Seralize(ar),读入真正的对象的信息.
7. 将对象的指针返回.
8. pMessg就指向一个对应的对象了.


 


MFC 六大关键技术之仿真
DECLARE_SERIAL / IMPLEMENT_SERIAL 宏
要将<< 和>> 两个运算子多载化,还要让Serialize 函数神不知鬼不觉地放入类别声明
之中,最好的作法仍然是使用宏。
类别之能够进行文件读写动作,前提是拥有动态生成的能力,所以,MFC 设计了两个宏
DECLARE_SERIAL 和IMPLEMENT_SERIAL:


#define DECLARE_SERIAL(class_name) \
DECLARE_DYNCREATE(class_name) \
friend CArchive& AFXAPI operator>>(CArchive& ar, class_name* &pOb);
#define IMPLEMENT_SERIAL(class_name, base_class_name, wSchema) \
CObject* PASCAL class_name::CreateObject() \
{ return new class_name; } \
_IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, wSchema, \
class_name::CreateObject) \
CArchive& AFXAPI operator>>(CArchive& ar, class_name* &pOb) \
{ pOb = (class_name*) ar.ReadObject(RUNTIME_CLASS(class_name)); \
return ar; } \


为了在每一个对象被处理(读或写)之前,能够处理琐屑的工作,诸如判断是否第一次
出现、记录版本号码、记录文件名等工作,CRuntimeClass 需要两个函数Load 和Store


struct CRuntimeClass
{
// Attributes
LPCSTR m_lpszClassName;
int m_nObjectSize;
UINT m_wSchema; // schema number of the loaded class
CObject* (PASCAL* m_pfnCreateObject)(); // NULL => abstract class
CRuntimeClass* m_pBaseClass;
CObject* CreateObject();
void Store(CArchive& ar) const;
static CRuntimeClass* PASCAL Load(CArchive& ar, UINT* pwSchemaNum);
// CRuntimeClass objects linked together in simple list
static CRuntimeClass* pFirstClass; // start of class list
CRuntimeClass* m_pNextClass; // linked list of registered classes
};


你已经在上一节看过Load 函数,当时为了简化,我把它的参数拿掉,改为由屏幕上获
得类别名称,事实上它应该是从文件中读一个类别名称。至于Store 函数,是把类别名
称写入文件中:


// Runtime class serialization code
CRuntimeClass* PASCAL CRuntimeClass::Load(CArchive& ar, UINT* pwSchemaNum)
{
WORD nLen;
char szClassName[64];
CRuntimeClass* pClass;
ar >> (WORD&)(*pwSchemaNum) >> nLen;
if (nLen >= sizeof(szClassName) || ar.Read(szClassName, nLen) != nLen)
return NULL;
szClassName[nLen] = ~\0~;
for (pClass = pFirstClass; pClass != NULL; pClass = pClass->m_pNextClass)
{
if (lstrcmp(szClassName, pClass->m_lpszClassName) == 0)
return pClass;
}
return NULL; // not found
}
void CRuntimeClass::Store(CArchive& ar) const
// stores a runtime class description
{
WORD nLen = (WORD)lstrlenA(m_lpszClassName);
ar << (WORD)m_wSchema << nLen;
ar.Write(m_lpszClassName, nLen*sizeof(char));
}
class CScribDoc : public CDocument
{
DECLARE_DYNCREATE(CScribDoc)
...
};
class CStroke : public CObject
{
DECLARE_SERIAL(CStroke)
public:


void Serialize(CArchive&);
...
};
class CRectangle : public CObject
{
DECLARE_SERIAL(CRectangle)
public:
void Serialize(CArchive&);
...
};
class CCircle : public CObject
{
DECLARE_SERIAL(CCircle)
public:
void Serialize(CArchive&);
...
};


以及在.CPP 档中做这样的动作:


IMPLEMENT_DYNCREATE(CScribDoc, CDocument)
IMPLEMENT_SERIAL(CStroke, CObject, 2)
IMPLEMENT_SERIAL(CRectangle, CObject, 1)
IMPLEMENT_SERIAL(CCircle, CObject, 1)


然后呢?分头设计CStroke、CRectangle 和CCircle 的Serialize 函数吧。
当然,毫不令人意外地,MFC 源代码中的CObList 和CDWordArray 有这样的内容:


// in header files
class CDWordArray : public CObject
{
DECLARE_SERIAL(CDWordArray)
public:
void Serialize(CArchive&);
...
};
class CObList : public CObject
{
DECLARE_SERIAL(CObList)
public:
void Serialize(CArchive&);
...
};
// in implementation files


IMPLEMENT_SERIAL(CObList, CObject, 0)
IMPLEMENT_SERIAL(CDWordArray, CObject, 0)


而CObject 也多了一个虚拟函数Serialize:
class CObject
{
public:
virtual void Serialize(CArchive& ar);
...
}
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 宝宝几个月能竖着抱 两个月宝宝可以竖着抱吗 两个月宝宝可以竖抱吗 几个月宝宝可以竖抱 几个月宝宝可以竖着抱 三个月宝宝可以竖着抱吗 三个月宝宝可以竖抱吗 新生儿几个月可以竖着抱 婴儿几个月可以竖着抱 婴儿什么时候可以竖着抱 婴儿什么时候可以竖抱 新生儿几个月可以竖抱 宝宝三个月可以竖着抱吗 小孩几个月可以竖着抱 宝宝什么时候可以竖着抱 宝宝什么时候可以竖抱 孩子几个月可以竖着抱 小孩几个月可以竖抱 孩子几个月可以竖抱 宝宝几个月才能竖着抱 三个月的宝宝可以竖着抱吗 两个多月的宝宝可以竖着抱吗 宝宝什么时候能竖着抱 两个月的宝宝可以竖着抱吗 几个月的宝宝可以竖着抱 几个月婴儿可以竖着抱 婴儿三个月可以竖抱吗 2个月的宝宝可以竖着抱吗 小宝宝几个月可以竖着抱 婴儿两个月可以竖抱吗 婴幼儿几个月可以竖着抱 一个月的宝宝可以竖着抱吗 多大的婴儿可以竖着抱 二个月的宝宝可以竖着抱吗 几个月的宝宝可以竖抱 多大的宝宝可以竖着抱 3个月的宝宝可以竖着抱吗 新生儿多大可以竖着抱 宝宝两个月可以竖着抱吗 两个月宝宝喜欢竖着抱 三个月的婴儿可以竖着抱吗