📄 ch22.htm
字号:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//EN">
<HTML>
<HEAD>
<META NAME="GENERATOR" Content="Symantec Visual Page Mac 1.1">
<TITLE>Teach Yourself Visual C++® 5 in 24 Hours -- Hour 22 -- Serialization</TITLE>
</HEAD>
<BODY TEXT="#000000" BGCOLOR="#FFFFFF">
<H1 ALIGN="CENTER"><IMG SRC="../button/sams.gif" WIDTH="171" HEIGHT="66" ALIGN="BOTTOM"
BORDER="0"><BR>
<FONT COLOR="#000077">Teach Yourself Visual C++® 5 in 24 Hours</FONT></H1>
<CENTER>
<P><A HREF="../ch21/ch21.htm"><IMG SRC="../button/previous.gif" WIDTH="128" HEIGHT="28"
ALIGN="BOTTOM" ALT="Previous chapter" BORDER="0"></A><A HREF="../ch23/ch23.htm"><IMG
SRC="../button/next.gif" WIDTH="128" HEIGHT="28" ALIGN="BOTTOM" ALT="Next chapter"
BORDER="0"></A><A HREF="../index.htm"><IMG SRC="../button/contents.gif" WIDTH="128"
HEIGHT="28" ALIGN="BOTTOM" ALT="Contents" BORDER="0"></A>
<HR>
</CENTER>
<H1 ALIGN="CENTER"><FONT COLOR="#000077">- Hour 22 -<BR>
Serialization</FONT></H1>
<P>Serialization is the method used by MFC programs to read and write application
data to files. In this hour, you will learn about
<UL>
<LI>Persistence and serialization
<LI>Serialization support in commonly used MFC classes
<LI>Macros and other MFC features that are used when implementing serialization
</UL>
<P>You will also create an example that uses serialization in a Document/View application.
<H2><FONT COLOR="#000077"><B>What Is Serialization?</B></FONT></H2>
<P><FONT COLOR="#000077"><B>New Term:</B></FONT><B> </B><I>Serialization</I> is the
process of storing the state of an object for the purpose of loading it at another
time.</P>
<P><FONT COLOR="#000077"><B>New Term:</B></FONT><B> </B>The property of an object
to be stored and loaded is <I>persistence</I>, which is also defined as the capability
of an object to remember its state between executions.</P>
<P>Serialization is the way in which classes derived from <TT>CDocument</TT> store
and retrieve data from an archive, which is usually a file. Figure 22.1 shows the
interaction between a serialized object and an archive.</P>
<P><A NAME="01"></A><A HREF="01.htm"><B>Figure 22.1.</B></A> <I><BR>
Serializing an object to and from an archive.</I></P>
<P>When an object is serialized, information about the type of object is written
to the storage along with information and data about the object. When an object is
deserialized, the same process happens in reverse, and the object is loaded and created
from the input stream.
<H2><FONT COLOR="#000077"><B>Why Use Serialization?</B></FONT></H2>
<P>The goal behind serialization is to make the storage of complex objects as simple
and reliable as the storage of the basic data types available in C++. You can store
a basic type, such as an <TT>int</TT>, in a file in the following way:</P>
<PRE><FONT COLOR="#0066FF"><TT>int nFoo = 5;</TT>
<TT>fileStream << nFoo;</TT>
</FONT></PRE>
<P>If a file contains an <TT>int</TT> value, it can be read from the stream in the
following way:</P>
<PRE><FONT COLOR="#0066FF"><TT>fileStream >> nFoo;</TT>
</FONT></PRE>
<P>A persistent object can be serialized and deserialized using a similar syntax,
no matter how complicated the object's internal structure. The alternative is to
create routines that understand how every object is implemented and handle the process
of storing and retrieving data from files.</P>
<P>Using serialization to store objects is much more flexible than writing specialized
functions that store data in a fixed format. Objects that are persistent are capable
of storing themselves, instead of relying on an external function to read and write
them to disk. This makes a persistent object much easier to reuse because the object
is more self-contained.</P>
<P>Persistent objects also help you easily write programs that are saved to storage.
An object that is serialized might be made up of many smaller objects that are also
serialized. Because individual objects are often stored in a collection, serializing
the collection also serializes all objects contained in the collection.
<H2><FONT COLOR="#000077"><B>A Document/View Serialization Example</B></FONT></H2>
<P>Using AppWizard, create an MDI project named Customers. This project uses serialization
to store a very simple list of customer names and email addresses, using a persistent
class named <TT>CUser</TT>. This project will serve as the basis for examples and
source code used in the remainder of this hour.
<H3><FONT COLOR="#000077"><B>Serializing the Customers Project</B></FONT></H3>
<P>In Hour 1, "Introducing Visual C++ 5," you used the insertion operator,
or <TT><<</TT>, to output a value to the screen. This operator is actually
the C++ left-shift operator, but it is overloaded so that whenever an output object
and variable are separated by a <TT><<</TT>, as in the following code line,
the variable is written to the output object:</P>
<PRE><FONT COLOR="#0066FF"><TT>file_object << data</TT>
</FONT></PRE>
<P>In a similar way, whenever input is performed and the objects are separated by
a <TT>>></TT>, as in the following code line, a new value for the variable
is retrieved from the input object:</P>
<PRE><FONT COLOR="#0066FF"><TT>file_object >> data</TT>
</FONT></PRE>
<P>In C++, unlike some other languages, input and output are controlled by the interaction
between file and variable objects. The exact process used for input and output is
controlled by the way in which the classes implement the <TT>>></TT> and <TT><<</TT>
operators.</P>
<P>For the topics in this hour, you create a persistent class named <TT>CUser</TT>,
along with the helper functions required to serialize a collection of <TT>CUser</TT>
objects. Each <TT>CUser</TT> object contains a customer name and email address.
<H3><FONT COLOR="#000077"><B>The MFC Classes Used for Serialization</B></FONT></H3>
<P>You use two MFC classes to serialize objects:
<UL>
<LI><TT>CArchive</TT> is almost always a file and is the object that other persistent
objects are serialized to or from.<BR>
<BR>
<LI><TT>CObject</TT> defines all the interfaces used to serialize objects to or from
a <TT>CArchive</TT> object.
</UL>
<P>Objects are serialized in one of two ways. As a rule of thumb, if an object is
derived from <TT>CObject</TT>, that object's <TT>Serialize</TT> member function is
called in the following way:</P>
<PRE><FONT COLOR="#0066FF"><TT>myObject.Serialize( ar );</TT>
</FONT></PRE>
<P>If the object isn't derived from <TT>CObject</TT>--such as a <TT>CRect</TT> object--you
should use the inser-tion operator in the following way:</P>
<PRE><FONT COLOR="#0066FF"><TT>ar << rcWnd;</TT>
</FONT></PRE>
<P>This insertion operator is overloaded in the same way it is for <TT>cout</TT>,
<TT>cin</TT>, and <TT>cerr</TT>, which were used in the first two hours for console
mode input and output.
<H4><FONT COLOR="#000077">Using the CObject Class</FONT></H4>
<P>You must use the <TT>CObject</TT> class for all classes that use the MFC class
library's built-in support for serialization. The <TT>CObject</TT> class contains
virtual functions that are used during serialization. In addition, the <TT>CArchive</TT>
class is declared as a "friend" class for <TT>CObject</TT>, providing it
access to private and protected member variables.</P>
<P>The most commonly used virtual function in <TT>CObject</TT> is <TT>Serialize</TT>,
which is called to serialize or deserialize the object from a <TT>CArchive</TT> object.
This function is declared as virtual so that any persistent object can be called
through a pointer to <TT>CObject</TT> in the following way:</P>
<PRE><FONT COLOR="#0066FF"><TT>CObject* pObj = GetNextObject();</TT>
<TT>pObj->Serialize( ar );</TT>
</FONT></PRE>
<P>As discussed later in the section "Using the Serialization Macros,"
when you're deriving a persistent class from <TT>CObject</TT>, you must use two macros
to help implement the serialization functions.
<H4><FONT COLOR="#000077">The CArchive Class</FONT></H4>
<P>The <TT>CArchive</TT> class is used to model a generic storage object. In most
cases, a <TT>CArchive</TT> object is attached to a disk file. In some cases, however,
the object might be connected to an object that only seems to be a file, like a memory
location or another type of storage.</P>
<P>When a <TT>CArchive</TT> object is created, it is defined as used for either input
or output but never both. You can use the <TT>IsStoring</TT> and <TT>IsLoading</TT>
functions to determine whether a <TT>CArchive</TT> object is used for input or output,
as shown in Listing 22.1.
<H4><FONT COLOR="#000077">TYPE: Listing 22.1. Using the CArchive::IsStoring function
to determine the serialization direction.</FONT></H4>
<PRE><FONT COLOR="#0066FF"><TT>CMyObject:Serialize( CArchive& ar )</TT>
<TT>{</TT>
<TT> if( ar.IsStoring() )</TT>
<TT> // Write object state to ar</TT>
<TT> else</TT>
<TT> // Read object state from ar</TT>
<TT>}</TT>
</FONT></PRE>
<H3><FONT COLOR="#000077"><B>Using the Insertion and Extraction Operators</B></FONT></H3>
<P>The MFC class library overloads the insertion and extraction operators for many
commonly used classes and basic types. You often use the insertion operator, <TT><<</TT>,
to serialize--or store--data to the <TT>CArchive</TT> object. You use the extraction
operator, <TT>>></TT>, to deserialize--or load--data from a <TT>CArchive</TT>
object.</P>
<P>These operators are defined for all basic C++ types, as well as a few commonly
used classes not derived from <TT>CObject</TT>, such as the <TT>CString</TT>, <TT>CRect</TT>,
and <TT>CTime</TT> classes. The insertion and extraction operators return a reference
to a <TT>CArchive</TT> object, enabling them to be chained together in the following
way:</P>
<PRE><FONT COLOR="#0066FF"><TT>archive << m_nFoo << m_rcClient << m_szName;</TT>
</FONT></PRE>
<P>When used with classes that are derived from <TT>CObject</TT>, the insertion and
extraction operators allocate the memory storage required to contain an object and
then call the object's <TT>Serialize</TT> member function. If you don't need to allocate
storage, you should call the <TT>Serialize</TT> member function directly.</P>
<P>As a rule of thumb, if you know the type of the object when it is deserialized,
call the <TT>Serialize</TT> function directly. In addition, you must always call
<TT>Serialize</TT> exclusively. If you use <TT>Serialize</TT> to load or store an
object, you must not use the insertion and extraction operators at any other time
with that object.
<H3><FONT COLOR="#000077"><B>Using the Serialization Macros</B></FONT></H3>
<P>There are two macros that you must use when creating a persistent class based
on <TT>CObject</TT>. Use the <TT>DECLARE_SERIAL</TT> macro in the class declaration
file and the <TT>IMPLEMENT_SERIAL</TT> macro in the class implementation file.
<H4><FONT COLOR="#000077">Declaring a Persistent Class</FONT></H4>
<P>The <TT>DECLARE_SERIAL</TT> macro takes a single parameter: the name of the class
to be serialized. An example of a class that can be serialized is provided in Listing
22.2. Save this source code in the Customers project directory in a file named <TT>Users.h</TT>.
<BLOCKQUOTE>
<P>
<HR>
<B> </B><FONT COLOR="#000077"><B>Time Saver:</B></FONT><B> </B>A good place to put
the <TT>DECLARE_SERIAL</TT> macro is on the first line of the class declaration,
where it serves as a reminder that the class can be serialized.
<HR>
</BLOCKQUOTE>
<H4><FONT COLOR="#000077">TYPE: Listing 22.2. The CUser class declaration.</FONT></H4>
<PRE><FONT COLOR="#0066FF"><TT>#ifndef CUSER</TT>
<TT>#define CUSER</TT>
<TT>class CUser : public CObject</TT>
<TT>{</TT>
<TT> DECLARE_SERIAL(CUser);</TT>
<TT>public:</TT>
<TT> // Constructors</TT>
<TT> CUser();</TT>
<TT> CUser( const CString& szName, const CString& szAddr );</TT>
<TT> // Attributes</TT>
<TT> void Set( const CString& szName, const CString& szAddr );</TT>
<TT> CString GetName() const;</TT>
<TT> CString GetAddr() const;</TT>
<TT> // Operations</TT>
<TT> virtual void Serialize( CArchive& ar );</TT>
<TT> // Implementation</TT>
<TT>private:</TT>
<TT> // The user's name</TT>
<TT> CString m_szName;</TT>
<TT> // The user's e-mail addresss</TT>
<TT> CString m_szAddr;</TT>
<TT>};</TT>
<TT>#endif CUSER</TT>
</FONT></PRE>
<H4><FONT COLOR="#000077">Defining a Persistent Class</FONT></H4>
<P>The <TT>IMPLEMENT_SERIAL</TT> macro takes three parameters and is usually placed
before any member functions are defined for a persistent class. The parameters for
<TT>IMPLEMENT_SERIAL</TT> are the following:
<UL>
<LI>The class to be serialized
<LI>The immediate base class of the class being serialized
<LI>The schema, or version number
</UL>
<P>The schema number is a version number for the class layout used when you're serializing
and deserializing objects. If the schema number of the data being loaded doesn't
match the schema number of the object reading the file, the program throws an exception.
The schema number should be incremented when changes are made that affect serialization,
such as adding a class member or changing the serialization order.</P>
<P>The member functions for the <TT>CUser</TT> class, including the <TT>IMPLEMENT_SERIAL</TT>
macro, are provided in Listing 22.3. Save this source code in the Customers project
directory as <TT>Users.cpp</TT>.
<H4><FONT COLOR="#000077">TYPE: Listing 22.3. The CUser member functions.</FONT></H4>
<PRE><FONT COLOR="#0066FF"><TT>#include "stdafx.h"</TT>
<TT>#include "Users.h"</TT>
<TT>IMPLEMENT_SERIAL( CUser, CObject, 1 );</TT>
<TT>CUser::CUser() { }</TT>
<TT>CUser::CUser( const CString& szName, const CString& szAddr )</TT>
<TT>{</TT>
<TT> Set( szName, szAddr );</TT>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -