新手指南-序列化篇之二
2008-04-01 16:29
417 查看
序列化初步(翻译)原著:http://www.codeproject.com/cpp/serialization_primer2.asp在第一部分,我们学到了如何通过CArchive类的serialize()函数来序列化一个简单对象。象下面的程序这样:int CFoo::serialize (CArchive* pArchive) { int nStatus = SUCCESS; // Serialize the object ... ASSERT (pArchive != NULL); TRY { if (pArchive->IsStoring()) { // Write employee name and id (*pArchive) << m_strName; (*pArchive) << m_nId; } else { // Read employee name and id (*pArchive) >> m_strName; (*pArchive) >> m_nId; } } CATCH_ALL (pException) { nStatus = ERROR; } END_CATCH_ALL return (nStatus); }这段代码有一个问题,如果我们错误的从数据文件中去读取并不存在的信息会怎么样?如果数据文件中在CString之后不是一个整数,那么serialize()函数将返回ERROR。这也不错,但是如果我们可以给错误定位,并返回一个更加详细的错误信息,比如INVALID_DATAFILE,那就更好了。我们可以通过对象签名来确认我们是在读取一个有效的数据文件。对象签名对象签名就是用一个字符串来识别一个对象。我们修改Cfoo类的定义来增加一个签名:class CFoo { ... // Methods public: ... CString getSignature(); // Data members ... protected: static const CString Signature; // object signature };签名在Foo.cpp中申明:// Static constants const CString CFoo::Signature = "FooObject";下一步,我们修改serialize()函数,在序列化对象的数据成员前先序列化这个签名。如果遇到无效的签名,或者签名丢失,那么可以认为我们在试图读取一个并不包含有Cfoo对象的数据存储。以下是读取有签名的对象的流程:
以下是流程实现代码:int CFoo::serialize (CArchive* pArchive) { int nStatus = SUCCESS; bool bSignatureRead = false; // Serialize the object ... ASSERT (pArchive != NULL); TRY { if (pArchive->IsStoring()) { // Write signature (*pArchive) << getSignature(); // Write employee name and id (*pArchive) << m_strName; (*pArchive) << m_nId; } else { // Read signature - complain if invalid CString strSignature; (*pArchive) >> strSignature; bSignatureRead = true; if (strSignature.Compare (getSignature()) != 0) { return (INVALID_DATAFILE); } // Read employee name and id (*pArchive) >> m_strName; (*pArchive) >> m_nId; } } CATCH_ALL (pException) { nStatus = bSignatureRead ? ERROR : INVALID_DATAFILE; } END_CATCH_ALL return (nStatus); }你必须确保你所有的对象都有唯一的签名。具体的签名内容并不重要。如果你正在开发一批产品,那么在公司范围内增加一个对象签名注册步骤是非常有帮助的。另一方面,开发者将不会给不同的对象使用相同的签名。如果你想让你的数据文件更难以被反解,那么你应该使用一个与对象名称没有明显任何关系的签名。版本如果你在产品的生命周期内进行升级,那么你也许要通过增加或者删除数据成员来修改Cfoo类结构。如果你只是为Cfoo发布新的版本,并且试图从数据存储中读取旧版本的对象,那么将会失败。这明显是不可以接受的。Cfoo的任何版本都应该可以让一个更旧版本恢复。换句话说,Cfoo的序列化方法应该是向前兼容的。这个问题通过给对象增加版本是很容易完成的。与给对象增加签名相同,我们增加一个整数常量来指定这个对象的版本号。class CFoo { ... // Methods public: ... CString getSignature(); int getVersion(); // Data members ... protected: static const CString Signature; // object signature static const int Version; // object version };对象的版本号在Foo.cpp中申明 // Static constants const CString CFoo::Signature = "FooObject"; const int CFoo::Version = 1;下一步,我们修改serialize()函数,在序列化签名之后,序列化对象的数据成员之前序列化版本。如果遇到更新的版本,我们试图读取一个对象不支持的版本,那么,在下面的例子中,我们就返回一个状态标志UNSUPPORTED_VERSION。int CFoo::serialize (CArchive* pArchive) { int nStatus = SUCCESS; bool bSignatureRead = false; bool bVersionRead = false; // Serialize the object ... ASSERT (pArchive != NULL); TRY { if (pArchive->IsStoring()) { // Write signature and version (*pArchive) << getSignature(); (*pArchive) << getVersion(); // Write employee name and id (*pArchive) << m_strName; (*pArchive) << m_nId; } else { // Read signature - complain if invalid CString strSignature; (*pArchive) >> strSignature; bSignatureRead = true; if (strSignature.Compare (getSignature()) != 0) { return (INVALID_DATAFILE); } // Read version - complain if unsupported int nVersion; (*pArchive) >> nVersion; bVersionRead = true; if (nVersion > getVersion()) { return (UNSUPPORTED_VERSION); } // Read employee name and id (*pArchive) >> m_strName; (*pArchive) >> m_nId; } } CATCH_ALL (pException) { nStatus = bSignatureRead && bVersionRead ? ERROR : INVALID_DATAFILE; } END_CATCH_ALL return (nStatus); }Cfoo的版本1包含两个数据成员:一个CString类型(m_strName)和一个int类型(m_nId)。如果我们在版本2中增加第三个成员(例如: int m_nDept)。那么我们必须决定,当读取一个旧版本时,m_nDept应该初始化为多少。在下面的例子中,我们初始化m_nDept为1,意味着这个职员的部门为“未知”。class CFoo { ... // Data members public: CString m_strName; // employee name int m_nId; // employee id int m_nDept; // department code (-1 = unknown) };我们需要将Foo.cpp中对象的版本号改为2。const int CFoo::Version = 2;最后,我们修改serialize()函数中读取对象的部分,如果我们读取的是一个旧版本的数据文件,将m_nDept初始化为-1。注意,文件保存总是用最新的版本。int CFoo::serialize (CArchive* pArchive) { ... // Serialize the object ... ASSERT (pArchive != NULL); TRY { if (pArchive->IsStoring()) { ... // Write employee name, id and department code (*pArchive) << m_strName; (*pArchive) << m_nId; (*pArchive) << m_nDept; } else { ... // Read employee name and id (*pArchive) >> m_strName; (*pArchive) >> m_nId; // Read department code (new in version 2) if (nVersion >= 2) { (*pArchive) >> m_nDept; } else { m_nDept = -1; // unknown } } } CATCH_ALL (pException) { nStatus = bSignatureRead && bVersionRead ? ERROR : INVALID_DATAFILE; } END_CATCH_ALL return (nStatus); }
以下是流程实现代码:int CFoo::serialize (CArchive* pArchive) { int nStatus = SUCCESS; bool bSignatureRead = false; // Serialize the object ... ASSERT (pArchive != NULL); TRY { if (pArchive->IsStoring()) { // Write signature (*pArchive) << getSignature(); // Write employee name and id (*pArchive) << m_strName; (*pArchive) << m_nId; } else { // Read signature - complain if invalid CString strSignature; (*pArchive) >> strSignature; bSignatureRead = true; if (strSignature.Compare (getSignature()) != 0) { return (INVALID_DATAFILE); } // Read employee name and id (*pArchive) >> m_strName; (*pArchive) >> m_nId; } } CATCH_ALL (pException) { nStatus = bSignatureRead ? ERROR : INVALID_DATAFILE; } END_CATCH_ALL return (nStatus); }你必须确保你所有的对象都有唯一的签名。具体的签名内容并不重要。如果你正在开发一批产品,那么在公司范围内增加一个对象签名注册步骤是非常有帮助的。另一方面,开发者将不会给不同的对象使用相同的签名。如果你想让你的数据文件更难以被反解,那么你应该使用一个与对象名称没有明显任何关系的签名。版本如果你在产品的生命周期内进行升级,那么你也许要通过增加或者删除数据成员来修改Cfoo类结构。如果你只是为Cfoo发布新的版本,并且试图从数据存储中读取旧版本的对象,那么将会失败。这明显是不可以接受的。Cfoo的任何版本都应该可以让一个更旧版本恢复。换句话说,Cfoo的序列化方法应该是向前兼容的。这个问题通过给对象增加版本是很容易完成的。与给对象增加签名相同,我们增加一个整数常量来指定这个对象的版本号。class CFoo { ... // Methods public: ... CString getSignature(); int getVersion(); // Data members ... protected: static const CString Signature; // object signature static const int Version; // object version };对象的版本号在Foo.cpp中申明 // Static constants const CString CFoo::Signature = "FooObject"; const int CFoo::Version = 1;下一步,我们修改serialize()函数,在序列化签名之后,序列化对象的数据成员之前序列化版本。如果遇到更新的版本,我们试图读取一个对象不支持的版本,那么,在下面的例子中,我们就返回一个状态标志UNSUPPORTED_VERSION。int CFoo::serialize (CArchive* pArchive) { int nStatus = SUCCESS; bool bSignatureRead = false; bool bVersionRead = false; // Serialize the object ... ASSERT (pArchive != NULL); TRY { if (pArchive->IsStoring()) { // Write signature and version (*pArchive) << getSignature(); (*pArchive) << getVersion(); // Write employee name and id (*pArchive) << m_strName; (*pArchive) << m_nId; } else { // Read signature - complain if invalid CString strSignature; (*pArchive) >> strSignature; bSignatureRead = true; if (strSignature.Compare (getSignature()) != 0) { return (INVALID_DATAFILE); } // Read version - complain if unsupported int nVersion; (*pArchive) >> nVersion; bVersionRead = true; if (nVersion > getVersion()) { return (UNSUPPORTED_VERSION); } // Read employee name and id (*pArchive) >> m_strName; (*pArchive) >> m_nId; } } CATCH_ALL (pException) { nStatus = bSignatureRead && bVersionRead ? ERROR : INVALID_DATAFILE; } END_CATCH_ALL return (nStatus); }Cfoo的版本1包含两个数据成员:一个CString类型(m_strName)和一个int类型(m_nId)。如果我们在版本2中增加第三个成员(例如: int m_nDept)。那么我们必须决定,当读取一个旧版本时,m_nDept应该初始化为多少。在下面的例子中,我们初始化m_nDept为1,意味着这个职员的部门为“未知”。class CFoo { ... // Data members public: CString m_strName; // employee name int m_nId; // employee id int m_nDept; // department code (-1 = unknown) };我们需要将Foo.cpp中对象的版本号改为2。const int CFoo::Version = 2;最后,我们修改serialize()函数中读取对象的部分,如果我们读取的是一个旧版本的数据文件,将m_nDept初始化为-1。注意,文件保存总是用最新的版本。int CFoo::serialize (CArchive* pArchive) { ... // Serialize the object ... ASSERT (pArchive != NULL); TRY { if (pArchive->IsStoring()) { ... // Write employee name, id and department code (*pArchive) << m_strName; (*pArchive) << m_nId; (*pArchive) << m_nDept; } else { ... // Read employee name and id (*pArchive) >> m_strName; (*pArchive) >> m_nId; // Read department code (new in version 2) if (nVersion >= 2) { (*pArchive) >> m_nDept; } else { m_nDept = -1; // unknown } } } CATCH_ALL (pException) { nStatus = bSignatureRead && bVersionRead ? ERROR : INVALID_DATAFILE; } END_CATCH_ALL return (nStatus); }