17.3.2 序列化文档
第一步是针对CSketcherDoc类实现Serialize()函数。在这个函数内,为了对CSketcherDoc的数据成员进行序列化,必须添加代码。在这个类中已经声明的数据成员如下所示:
- protected:
- ElementType m_Element; // Current element type
- ElementColor m_Color; // Current drawing color
- std::list<std::shared_ptr<CElement>> m_Sketch; // A list containing the sketch
- int m_PenWidth; // Current pen width
- CSize m_DocSize; // Document size
这些数据成员必须可序列化,以允许CSketcherDoc对象可反序列化。需要做的仅仅是在这个类的Serialize()成员中插入存储和检索这5个数据成员的语句。但这还是有一个问题。对象list<std::shared_ ptr<CElement>>不是可序列化的,因为其模板不是从CObject派生的。事实上,STL容器都不是可序列化的,因此必须自己处理STL容器的序列化。
但问题还有希望解决。如果能够序列化容器中的指针指向的对象,那么当把它读回来时,就能够重新构建容器。
MFC定义了可序列化的容器类,如CList。但是,如果在Sketcher中使用这些类,就无法了解如何使一个类可序列化了。
下面的代码可以实现文档对象的序列化:
- void CSketcherDoc::Serialize(CArchive& ar)
- {
- if (ar.IsStoring())
- {
- ar << static_cast<COLORREF>(m_Color) // Store the current color
- << static_cast<int>(m_Element) // the element type as an integer
- << m_PenWidth // and the current pen width
- << m_DocSize; // and the current document size
- ar << m_Sketch.size(); // Store the number of elements in the list
- // Now store the elements from the list
- for(auto& pElement : m_Sketch)
- ar << pElement.get(); // Store the element pointer
- }
- else
- {
- COLORREF color(0);
- int elementType(0);
- ar >> color // Retrieve the current color
- >> elementType // the element type as an integer
- >> m_PenWidth // and the current pen width
- >> m_DocSize; // and the current document size
- m_Color = static_cast<ElementColor>(color);
- m_Element = static_cast<ElementType>(elementType);
- // Now retrieve all the elements and store in the list
- size_t elementCount(0); // Count of number of elements
- ar >> elementCount; // retrieve the element count
- CElement* pElement;
- for(size_t i = 0 ; i < elementCount ; ++i)
- {
- ar >> pElement;
- m_Sketch.push_back(std::shared_ptr<CElement>(pElement));
- }
- }
- }
对于其中的4个数据成员,只使用了在CArchive类中重载的析取和插入运算符。这不适用于数据成员m_Color,因为ElementColor类型是不可序列化的。但可以把它的类型转换为可序列化的COLORREF,因为类型COLORREF和类型long相同。m_Element成员的类型是ElementType,序列化过程不直接处理它。但是,可以将它强制转换成一个整数以便序列化,然后在将此值强制转换回ElementType之前,将它反序列化为一个整数。
对于元素列表m_Sketch,首先将元素数量的计数存储到存档的列表中,因为读回元素时将需要它。然后在for循环中将shared_ptr对象包含的元素指针从列表写到存档中。此序列化机制将识别出需要被指向的对象来重新构建文档,而且负责将它们写到存档中。
if的else子句处理从档案中读回文档对象。使用析取运算符从存档中检索前4个成员,它们的顺序与写到存档中的顺序相同。元素类型和颜色读到本地整型变量elementType和color中,然后按照正确的类型存储到m_Element和m_Color成员中。
接下来读取存档中记录的元素数量,并将它存储在本地elementCount中。最后,使用elementCount来控制for循环,此for循环从档案读回元素,并将它们存储到列表中。注意,不需要针对元素最初是在堆上创建的这样一个事实做任何特殊的工作。序列化机制自动负责还原堆上的元素。我们只需把每个对象的指针传递给shared_ptr<CElement>构造函数。
如果您在反序列化一个对象时,不清楚list<shared_ptr<CElement>>对象来自哪里,则会由序列化过程使用CSketcherDoc类的默认构造函数来创建。这就是基本文档对象及其未初始化数据成员的创建方式,很神奇吧。
序列化文档类数据成员时就需要做这么多,但在序列化列表中的元素时,为了存储和检索元素本身,将调用元素类的Serialize()函数,所以还需要针对这些类实现序列化。