序列化入门 - 第 3 部分






4.76/5 (32投票s)
本教程介绍如何序列化复杂对象。
本文是关于序列化的 3 部分教程的第三部分。
在前面两部分中,我们学习了如何为序列化提供通用的健壮支持。在本文中,我们将学习序列化任何类型对象的具体规则。有 4 种一般情况需要考虑。每种情况都建立在前一种的基础上。
- 序列化简单类
- 序列化派生类
- 序列化同质集合类
- 序列化异质集合类
我们的 serialize()
方法将返回以下状态码之一
成功
无效格式
不支持的版本
读取错误
写入错误
序列化简单类
“简单类”被定义为没有父类且不是集合类的对象。要序列化简单类,请执行以下操作:
- 序列化对象的签名和版本
- 序列化对象的成员(如果有)
在下面的示例中,Point
类包含 2 个 int
成员,它们表示点的坐标。对象的签名和版本被定义为 static
成员(m_strSignature
和 m_nVersion
),因为它们适用于所有 Point
实例。
int Point::serialize
(CArchive* pArchive)
{
ASSERT (pArchive != NULL);
// Step 1: Serialize signature and version
int nVersion;
try {
if (pArchive->IsStoring()) {
(*pArchive) << Point::m_strSignature;
(*pArchive) << Point::m_nVersion;
} else {
CString strSignature;
(*pArchive) >> strSignature;
if (strSignature != Point::m_strSignature)
return (Status::InvalidFormat);
(*pArchive) >> nVersion;
if (nVersion > Point::m_nVersion;)
return (Status::UnsupportedVersion);
}
// Step 2: Serialize members
if (pArchive->IsStoring()) {
(*pArchive) << m_nX;
(*pArchive) << m_nY;
} else {
(*pArchive) >> m_nX;
(*pArchive) >> m_nY;
}
}
catch (CException* pException) {
// A read/write error occurred
pException->Delete();
if (pArchive->IsStoring())
return (Status::WriteError);
return (Status::ReadError);
}
// Object was successfully serialized
return (Status::Success);
}
序列化派生类
在本讨论中,派生类是指从简单类派生而来且不是集合类的类。要序列化派生类,请执行以下操作:
- 序列化对象的签名和版本
- 序列化对象的基类 << 附加步骤
- 序列化对象的成员(如果有)
在下面的示例中,ColoredPoint
类派生自 Point
,并包含一个额外的名为 m_nColor
的 int
成员,用于指定点的颜色。与所有可序列化的类一样,ColoredPoint
也定义了 static
签名和版本。
int ColoredPoint::serialize
(CArchive* pArchive)
{
ASSERT (pArchive != NULL);
// Step 1: Serialize signature and version
int nVersion;
try {
if (pArchive->IsStoring()) {
(*pArchive) << ColoredPoint::m_strSignature;
(*pArchive) << ColoredPoint::m_nVersion;
} else {
CString strSignature;
(*pArchive) >> strSignature;
if (strSignature != ColoredPoint::m_strSignature)
return (Status::InvalidFormat);
(*pArchive) >> nVersion;
if (nVersion > ColoredPoint::m_nVersion;)
return (Status::UnsupportedVersion);
}
// Step 2: Serialize the base class
int nStatus = Point::serialize (pArchive);
if (nStatus != Status::Success)
return (nStatus);
// Step 3: Serialize members
if (pArchive->IsStoring())
(*pArchive) << m_nColor;
else
(*pArchive) >> m_nColor;
}
catch (CException* pException) {
// A read/write error occurred
pException->Delete();
if (pArchive->IsStoring())
return (Status::WriteError);
return (Status::ReadError);
}
// Object was successfully serialized
return (Status::Success);
}
序列化同质集合类
同质集合类用于存储相同类型对象的动态大小集合。要序列化同质集合类,请执行以下操作:
- 序列化对象的签名和版本
- 序列化对象的基类(如果有)
- 序列化集合中的项目数 << 附加步骤
- 序列化集合中的每个对象 << 附加步骤
- 序列化对象的其他成员(如果有)
在下面的示例中,ColoredPointList
类是 ColoredPoint
对象集合。为简单起见,ColoredPointList
使用 CPtrArray
来存储对象。与所有可序列化的类一样,ColoredPointList
也定义了静态签名和版本。ColoredPointList
的外观如下:
class ColoredPointList
{
// Construction/destruction
public:
ColoredPointList::ColoredPointList();
virtual ColoredPointList::~ColoredPointList();
// Attributes
public:
static const CString m_strSignature;
static const int m_nVersion;
// Operations
public:
int serialize (CArchive* pArchive);
// Members
protected:
CPtrArray m_coloredPoints;
}
以下是其序列化方式:
int ColoredPointList::serialize
(CArchive* pArchive)
{
ASSERT (pArchive != NULL);
int nStatus = Status::Success;
// Step 1: Serialize signature and version
int nVersion;
try {
if (pArchive->IsStoring()) {
(*pArchive) << ColoredPointList::m_strSignature;
(*pArchive) << ColoredPointList::m_nVersion;
} else {
CString strSignature;
(*pArchive) >> strSignature;
if (strSignature != ColoredPointList::m_strSignature)
return (Status::InvalidFormat);
(*pArchive) >> nVersion;
if (nVersion > ColoredPointList::m_nVersion;)
return (Status::UnsupportedVersion);
}
// Step 2: Serialize base class (if any)
//
// Nothing to do since ColoredPointList isn't derived from anything.
// But if it was derived from BaseColoredPointList, we'd do:
//
// nStatus = BaseColoredPointList::serialize (pArchive);
// if (nStatus != Status::Success)
// return (nStatus);
// Step 3: Serialize number of items in collection
int nItems = 0;
if (pArchive->IsStoring()) {
nItems = m_coloredPoints.GetSize();
(*pArchive) << nItems;
} else
(*pArchive) >> nItems;
// Step 4: Serialize each object in collection
for (int nObject=0; (nObject < nItems); nObject++) {
// 4a: Point to object being serialized
ColoredPoint* pColoredPoint = NULL;
if (pArchive->IsStoring())
pColoredPoint = (ColoredPoint *) m_coloredPoints.GetAt (nObject);
else
pColoredPoint = new ColoredPoint();
ASSERT (pColoredPoint != NULL);
// 4b: Serialize it
nStatus = pColoredPoint->serialize (pArchive);
if (nStatus != Status::Success)
return (nStatus);
if (!pArchive->IsStoring())
m_coloredPoints.Add (pColoredPoint);
}
// Step 5: Serialize object's other members (if any)
//
// Nothing to do since ColoredPointList doesn't have any other
// members. But if it contained an int (m_nSomeInt) and a Foo
// object (m_foo), we'd do:
//
// if (pArchive->IsStoring())
// (*pArchive) << m_nSomeInt;
// else
// (*pArchive) >> m_nColor;
//
// nStatus = m_foo::serialize (pArchive);
// if (nStatus != Status::Success)
// return (nStatus);
}
catch (CException* pException) {
// A read/write error occurred
pException->Delete();
if (pArchive->IsStoring())
return (Status::WriteError);
return (Status::ReadError);
}
// Object was successfully serialized
return (Status::Success);
}
序列化异质集合类
异质集合类用于存储可能包含不同类型对象的动态大小集合。要序列化异质集合类,请执行以下操作:
- 序列化对象的签名和版本
- 序列化对象的基类(如果有)
- 序列化集合中的项目数
- 对于集合中的每个对象:
- 序列化该对象的签名 << 附加步骤
- 然后序列化该对象
- 序列化对象的其他成员(如果有)
您会注意到,序列化异质集合的唯一附加步骤是 4(a),即我们在序列化每个对象之前先序列化其签名。这在读回数据时非常有用。当我们序列化同质集合时,我们处理的是相同类型的对象(在前面的示例中是 ColoredPoint
)。要读入一个 ColoredPoint
,我们在堆上构造它,然后调用其 serialize()
方法。
ColoredPoint* pColoredPoint = new ColoredPoint();
nStatus = pColoredPoint->serialize (pArchive);
当处理异质集合时,我们需要在实际序列化之前知道要读回的对象类型。这就是对象签名发挥作用的地方。由于我们在输出时保存了签名,所以我们可以在输入时构造一个适当类型的对象。
// Read object signature
CString strSignature;
pArchive >> strSignature;
// Construct object of appropriate type
ISerializable* pObject = NULL;
if (strSignature == ColoredPoint::m_strSignature)
pObject = new ColoredPoint();
else
if (strSignature == Line::m_strSignature)
pObject = new Line();
else
if (strSignature == Rectangle::m_strSignature)
pObject = new Rectangle();
else
return (Status::InvalidFormat);
ASSERT (pObject != NULL);
// Read it back in
nStatus = pObject->serialize (pArchive);
在上面的代码片段中,ColoredPoint
、Line
和 Rectangle
都(最终)派生自一个公共基类 ISerializable
,它只是一个包含纯 virtual
方法的 abstract
基类(换句话说,一个“接口”)。ISerializable
定义了 getSignature()
、getVersion()
和 serialize()
方法。
class ISerializable
{
// Construction/destruction
public:
ISerializable::ISerializable()
{ }
virtual ISerializable::~ISerializable()
{ }
// Operations
public:
// Get the object's signature
virtual CString getSignature() = 0;
// Get the object's version
virtual int getVersion() = 0;
// Serialize the object
virtual int serialize (CArchive* pArchive) = 0;
}
好了,让我们序列化我们的异质集合。在下面的示例中,ShapeList
类是一个包含不同数量的 ColoredPoint
、Line
和 Rectangle
对象的集合,这些对象都派生自 ISerializable
。您可以将这些类看作是“实现了 ISerializable
接口”。
int ShapeList::serialize
(CArchive* pArchive)
{
ASSERT (pArchive != NULL);
int nStatus = Status::Success;
// Step 1: Serialize signature and version
int nVersion;
try {
if (pArchive->IsStoring()) {
(*pArchive) << ShapeList::m_strSignature;
(*pArchive) << ShapeList::m_nVersion;
} else {
CString strSignature;
(*pArchive) >> strSignature;
if (strSignature != ShapeList::m_strSignature)
return (Status::InvalidFormat);
(*pArchive) >> nVersion;
if (nVersion > ShapeList::m_nVersion;)
return (Status::UnsupportedVersion);
}
// Step 2: Serialize base class (if any)
//
// Nothing to do since ShapeList isn't derived from anything.
// But if it was derived from BaseShapeList, we'd do:
//
// nStatus = BaseShapeList::serialize (pArchive);
// if (nStatus != Status::Success)
// return (nStatus);
// Step 3: Serialize number of items in collection
int nItems = 0;
if (pArchive->IsStoring()) {
nItems = m_shapes.GetSize();
(*pArchive) << nItems;
} else
(*pArchive) >> nItems;
// Step 4: Serialize each object in collection
for (int nObject=0; (nObject < nItems); nObject++) {
// 4a: First serialize object's signature
CString strSignature;
if (pArchive->IsStoring())
(*pArchive) << pObject->getSignature();
else
(*pArchive) >> strSignature;
//
// 4b: Then serialize object
//
// 4b (1): Point to object being serialized
ISerializable* pObject = NULL;
if (pArchive->IsStoring())
pObject = (ISerializable *) m_shapes.GetAt (nObject);
else {
if (strSignature == ColoredPoint::m_strSignature)
pObject = new ColoredPoint();
else
if (strSignature == Line::m_strSignature)
pObject = new Line();
else
if (strSignature == Rectangle::m_strSignature)
pObject = new Rectangle();
else
return (Status::InvalidFormat);
}
ASSERT (pObject != NULL);
// 4b (2): Serialize it
nStatus = pObject->serialize (pArchive);
if (nStatus != Status::Success)
return (nStatus);
if (!pArchive->IsStoring())
m_shapes.Add (pObject);
}
// Step 5: Serialize object's other members (if any)
//
// Nothing to do since ShapeList doesn't have any other
// members. But if it contained an int (m_nSomeInt) and
// a Foo object (m_foo), we'd do:
//
// if (pArchive->IsStoring())
// (*pArchive) << m_nSomeInt;
// else
// (*pArchive) >> m_nColor;
//
// nStatus = m_foo::serialize (pArchive);
// if (nStatus != Status::Success)
// return (nStatus);
}
catch (CException* pException) {
// A read/write error occurred
pException->Delete();
if (pArchive->IsStoring())
return (Status::WriteError);
return (Status::ReadError);
}
// Object was successfully serialized
return (Status::Success);
}
类工厂
您可以使用“类工厂”来根据签名提供新类的实例,从而替换代码片段中丑陋的 if
语句。以下是一些您可能想看的文章:
- 通用类工厂,作者:Robert A. T. Káldy
- 工厂方法(创建型)设计模式,作者:Gopalan Suresh Raj
- 抽象工厂模式,作者:Mark Grand
尽管这些文章的复杂程度各不相同,但它们背后的基本思想是相同的。类工厂只不过是一个类,它提供一个命名恰当的 static
方法(例如:create()
),该方法提供特定类型的对象。您可以将讨厌的 if
语句隐藏在工厂的 create()
方法中,从而稍微清理一下代码。
...
// Construct object of appropriate type
ISerializable* pObject = MyClassFactory::create (strSignature);
ASSERT (pObject != NULL);
...
结论
虽然本入门教程提到了 CString
和 CPtrArray
等 MFC 对象,但序列化并非 MFC 特有。任何需要将信息保存到持久存储并从中恢复的软件都会执行此常见操作。
修订历史
- 2005 年 6 月 24 日
修正示例代码中的拼写错误(感谢 J Roy!) - 2005 年 6 月 13 日
修正示例代码中的拼写错误(感谢 Mohammed Tarik!) - 2004 年 4 月 26 日
修正示例代码中的拼写错误(感谢 David Paul Jones!) - 2004 年 4 月 24 日
添加了急需的示例 - 2002 年 2 月 18 日
初始版本