65.9K
CodeProject 正在变化。 阅读更多。
Home

序列化入门 - 第 3 部分

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.76/5 (32投票s)

2002年2月18日

CPOL

5分钟阅读

viewsIcon

266581

本教程介绍如何序列化复杂对象。

本文是关于序列化的 3 部分教程的第三部分。

  • 第一部分 介绍了序列化的基础知识。
  • 第二部分 解释了如何优雅地处理读取无效数据存储和支持版本控制。
  • 第三部分介绍如何序列化复杂对象。

在前面两部分中,我们学习了如何为序列化提供通用的健壮支持。在本文中,我们将学习序列化任何类型对象的具体规则。有 4 种一般情况需要考虑。每种情况都建立在前一种的基础上。

  • 序列化简单类
  • 序列化派生类
  • 序列化同质集合类
  • 序列化异质集合类

我们的 serialize() 方法将返回以下状态码之一

  • 成功
  • 无效格式
  • 不支持的版本
  • 读取错误
  • 写入错误

序列化简单类

“简单类”被定义为没有父类且不是集合类的对象。要序列化简单类,请执行以下操作:

  1. 序列化对象的签名和版本
  2. 序列化对象的成员(如果有)

在下面的示例中,Point 类包含 2 个 int 成员,它们表示点的坐标。对象的签名和版本被定义为 static 成员(m_strSignaturem_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);
  }

序列化派生类

在本讨论中,派生类是指从简单类派生而来且不是集合类的类。要序列化派生类,请执行以下操作:

  1. 序列化对象的签名和版本
  2. 序列化对象的基类 << 附加步骤
  3. 序列化对象的成员(如果有)

在下面的示例中,ColoredPoint 类派生自 Point,并包含一个额外的名为 m_nColorint 成员,用于指定点的颜色。与所有可序列化的类一样,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);
  }

序列化同质集合类

同质集合类用于存储相同类型对象的动态大小集合。要序列化同质集合类,请执行以下操作:

  1. 序列化对象的签名和版本
  2. 序列化对象的基类(如果有)
  3. 序列化集合中的项目数 << 附加步骤
  4. 序列化集合中的每个对象 << 附加步骤
  5. 序列化对象的其他成员(如果有)

在下面的示例中,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);
  }

序列化异质集合类

异质集合类用于存储可能包含不同类型对象的动态大小集合。要序列化异质集合类,请执行以下操作:

  1. 序列化对象的签名和版本
  2. 序列化对象的基类(如果有)
  3. 序列化集合中的项目数
  4. 对于集合中的每个对象:
    1. 序列化该对象的签名 << 附加步骤
    2. 然后序列化该对象
  5. 序列化对象的其他成员(如果有)

您会注意到,序列化异质集合的唯一附加步骤是 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);

在上面的代码片段中,ColoredPointLineRectangle 都(最终)派生自一个公共基类 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 类是一个包含不同数量的 ColoredPointLineRectangle 对象的集合,这些对象都派生自 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 语句。以下是一些您可能想看的文章:

尽管这些文章的复杂程度各不相同,但它们背后的基本思想是相同的。类工厂只不过是一个类,它提供一个命名恰当的 static 方法(例如:create()),该方法提供特定类型的对象。您可以将讨厌的 if 语句隐藏在工厂的 create() 方法中,从而稍微清理一下代码。

  ...
  // Construct object of appropriate type
  ISerializable* pObject = MyClassFactory::create (strSignature);
  ASSERT (pObject != NULL);
  ...

结论

虽然本入门教程提到了 CStringCPtrArray 等 MFC 对象,但序列化并非 MFC 特有。任何需要将信息保存到持久存储并从中恢复的软件都会执行此常见操作。

修订历史

  • 2005 年 6 月 24 日
    修正示例代码中的拼写错误(感谢 J Roy!)
  • 2005 年 6 月 13 日
    修正示例代码中的拼写错误(感谢 Mohammed Tarik!)
  • 2004 年 4 月 26 日
    修正示例代码中的拼写错误(感谢 David Paul Jones!)
  • 2004 年 4 月 24 日
    添加了急需的示例
  • 2002 年 2 月 18 日
    初始版本
© . All rights reserved.