序列化入门 - 第 2 部分






4.73/5 (21投票s)
本教程介绍如何在序列化过程中处理无效数据存储和支持版本控制。
本文是关于序列化的三部分教程的第二部分。
在第一部分中,我们看到了如何使用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
后跟一个int
,我们的serialize()
方法将返回错误。这很好,但如果我们能识别这种情况并返回一个更具体的状态码,比如INVALID_DATAFILE
,那就更好了。我们可以通过使用对象签名来检查我们是否正在读取有效的数据文件(即包含CFoo
对象的那个)。
对象签名
对象签名只是一个标识对象的字符串(例如:“FooObject
”)。我们通过修改类定义来为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 包含 2 个数据成员 - 一个CString
(m_strName
)和一个int
(m_nId
)。 如果我们在版本 2 中添加第三个成员(例如:int
m_nDept
),我们需要决定在读取旧版本的对象时将m_nDept
初始化为什么。 在此示例中,我们将m_nDept
初始化为-1
,表示员工的部门代码为“Unknown
”。
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);
}
结论
到目前为止,我们已经处理了为序列化简单对象(即那些包含易于序列化的数据类型)提供强大的支持。 在 第三部分 中,我们将看到如何序列化任何类型的对象。