mirror of
https://github.com/boostorg/serialization.git
synced 2026-01-19 04:42:10 +00:00
524 lines
19 KiB
HTML
524 lines
19 KiB
HTML
<!doctype HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
|
|
<html>
|
|
<!--
|
|
(C) Copyright 2002-4 Robert Ramey - http://www.rrsd.com .
|
|
Use, modification and distribution is subject to the Boost Software
|
|
License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
|
|
http://www.boost.org/LICENSE_1_0.txt)
|
|
-->
|
|
<head>
|
|
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
|
|
<link rel="stylesheet" type="text/css" href="../../../boost.css">
|
|
<link rel="stylesheet" type="text/css" href="style.css">
|
|
<title>Serialization - Tutorial</title>
|
|
</head>
|
|
<body link="#0000ff" vlink="#800080">
|
|
<table border="0" cellpadding="7" cellspacing="0" width="100%" summary="header">
|
|
<tr>
|
|
<td valign="top" width="300">
|
|
<h3><a href="../../../index.htm"><img height="86" width="277" alt="C++ Boost" src="../../../c++boost.gif" border="0"></a></h3>
|
|
</td>
|
|
<td valign="top">
|
|
<h1 align="center">Serialization</h1>
|
|
<h2 align="center">Tutorial</h2>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
<hr>
|
|
<dl class="page-index">
|
|
<dt><a href="#simplecase">A Very Simple Case</a>
|
|
<dt><a href="#nonintrusiveversion">Non Intrusive Version</a>
|
|
<dt><a href="#serializablemembers">Serializable Members</a>
|
|
<dt><a href="#derivedclasses">Derived Classes</a>
|
|
<dt><a href="#pointers">Pointers</a>
|
|
<dt><a href="#arrays">Arrays</a>
|
|
<dt><a href="#stl">STL Collections</a>
|
|
<dt><a href="#versioning">Class Versioning</a>
|
|
<dt><a href="#splitting">Splitting <code style="white-space: normal">serialize</code> into <code style="white-space: normal">save/load</code></a>
|
|
<dt><a href="#archives">Archives</a>
|
|
</dl>
|
|
An output archive is similar to an output data stream. Data can be saved to the archive
|
|
with either the <<: or the & operator:
|
|
<pre><code>
|
|
ar << data;
|
|
ar & data;
|
|
</code></pre>
|
|
An input archive is similar to an input datastream. Data can be loaded from the archive
|
|
with either the >> or the & operator.
|
|
<pre><code>
|
|
ar >> data;
|
|
ar & data;
|
|
</code></pre>
|
|
<p>
|
|
When these operators are invoked for primitive data types, the data is simply saved/loaded
|
|
to/from the archive. When invoked for class data types, the class
|
|
<code style="white-space: normal">serialize</code> function is invoked. Each
|
|
<code style="white-space: normal">serialize</code> function is uses the above operators
|
|
to save/load its data members. This process will continue in a recursive manner until
|
|
all the data contained in the class is saved/loaded.
|
|
|
|
<h3><a name="simplecase">A Very Simple Case</a></h3>
|
|
These operators are used inside the <code style="white-space: normal">serialize</code>
|
|
function> to save and load class data members.
|
|
<p>
|
|
Included in this library is a program called
|
|
<a href="../example/demo.cpp" target="demo_cpp">demo.cpp</a> which illustrates how
|
|
to use this system. Below we excerpt code from this program to
|
|
illustrate with the simplest possible case how this library is
|
|
intended to be used.
|
|
<pre>
|
|
<code>
|
|
#include <fstream>
|
|
|
|
// include headers that implement a archive in simple text format
|
|
#include <boost/archive/text_oarchive.hpp>
|
|
#include <boost/archive/text_iarchive.hpp>
|
|
|
|
/////////////////////////////////////////////////////////////
|
|
// gps coordinate
|
|
//
|
|
// illustrates serialization for a simple type
|
|
//
|
|
class gps_position
|
|
{
|
|
private:
|
|
friend class boost::serialization::access;
|
|
// When the class Archive corresponds to an output archive, the
|
|
// & operator is defined similar to <<. Likewise, when the class Archive
|
|
// is a type of input archive the & operator is defined similar to >>.
|
|
template<class Archive>
|
|
void serialize(Archive & ar, const unsigned int version)
|
|
{
|
|
ar & degrees;
|
|
ar & minutes;
|
|
ar & seconds;
|
|
}
|
|
int degrees;
|
|
int minutes;
|
|
float seconds;
|
|
public:
|
|
gps_position(){};
|
|
gps_position(int d, int m, float s) :
|
|
degrees(d), minutes(m), seconds(s)
|
|
{}
|
|
};
|
|
|
|
void main() {
|
|
// create and open a character archive for output
|
|
std::ofstream ofs("filename");
|
|
boost::archive::text_oarchive oa(ofs);
|
|
|
|
// create class instance
|
|
const gps_position g(35, 59, 24.567f);
|
|
// write class instance to archive
|
|
oa << g;
|
|
// close archive
|
|
ofs.close();
|
|
|
|
// ... some time later restore the class instance to its orginal state
|
|
// create and open an archive for input
|
|
std::ifstream ifs("filename", std::ios::binary);
|
|
boost::archive::text_iarchive ia(ifs);
|
|
// read class state from archive
|
|
gps_position newg;
|
|
ia >> newg;
|
|
// close archive
|
|
ifs.close();
|
|
}
|
|
</code>
|
|
</pre>
|
|
<p>For each class to be saved via serialization, there must exist a function to
|
|
save all the class members which define the state of the class.
|
|
For each class to be loaded via serialization, there must exist a function to
|
|
load theese class members in the same sequence as they were saved.
|
|
In the above example, these functions are generated by the
|
|
template member function <code style="white-space: normal">serialize</code>.
|
|
|
|
<h3><a name="nonintrusiveversion">Non Intrusive Version</a></h3>
|
|
<p>The above formulation is intrusive. That is, it requires
|
|
that classes whose instances are to be serialized be
|
|
altered. This can be inconvenient in some cases.
|
|
An equivalent alternative formulation permitted by the
|
|
system would be:
|
|
<pre><code>
|
|
#include <boost/archive/text_oarchive.hpp>
|
|
#include <boost/archive/text_iarchive.hpp>
|
|
|
|
class gps_position
|
|
{
|
|
public:
|
|
int degrees;
|
|
int minutes;
|
|
float seconds;
|
|
gps_position(){};
|
|
gps_position(int d, int m, float s) :
|
|
degrees(d), minutes(m), seconds(s)
|
|
{}
|
|
};
|
|
|
|
namespace boost {
|
|
namespace serialization {
|
|
|
|
template<class Archive>
|
|
void serialize(Archive & ar, gps_position & g, const unsigned int version)
|
|
{
|
|
ar & g.degrees;
|
|
ar & g.minutes;
|
|
ar & g.seconds;
|
|
}
|
|
|
|
} // namespace serialization
|
|
} // namespace boost
|
|
</code></pre>
|
|
<p>
|
|
In this case the generated serialize functions are not members of the
|
|
<code style="white-space: normal">gps_position</code> class. The two formulations function
|
|
in exactly the same way.
|
|
<p>
|
|
The main application of non-intrusive serialization is to permit serialization
|
|
to be implemented for classes without changing the class definition.
|
|
In order for this to be possible, the class must expose enough information
|
|
to reconstruct the class state. In this example, we presumed that the
|
|
class had <code style="white-space: normal">public</code> members - not a common occurence. Only
|
|
classes which expose enough information to save and restore the class
|
|
state will be serializable without changing the class definition.
|
|
<h3><a name="serializablemembers">Serializable Members</a></h3>
|
|
<p>
|
|
A serializable class with serializable members would look like this:
|
|
<pre><code>
|
|
class bus_stop
|
|
{
|
|
friend class boost::serialization::access;
|
|
template<class Archive>
|
|
void serialize(Archive & ar, const unsigned int version)
|
|
{
|
|
ar & latitude;
|
|
ar & longitude;
|
|
}
|
|
gps_position latitude;
|
|
gps_position longitude;
|
|
protected:
|
|
bus_stop(const gps_position & lat_, const gps_position & long_) :
|
|
latitude(lat_), longitude(long_)
|
|
{}
|
|
public:
|
|
bus_stop(){}
|
|
// See item # 14 in Effective C++ by Scott Meyers.
|
|
// re non-virtual destructors in base classes.
|
|
virtual ~bus_stop(){}
|
|
};
|
|
</code></pre>
|
|
<p>That is, members of class type are serialized just as
|
|
members of primitive types are.
|
|
<p>
|
|
Note that saving an instance of the class <code style="white-space: normal">bus_stop</code>
|
|
with one of the archive operators will invoke the
|
|
<code style="white-space: normal">serialize</code> function which saves
|
|
<code style="white-space: normal">latitude</code> and
|
|
<code style="white-space: normal">longitude</code>. Each of these in turn will be saved by invoking
|
|
<code style="white-space: normal">serialize</code> in the definition of
|
|
<code style="white-space: normal">gps_position</code>. In this manner the whole
|
|
data structure is saved by the application of an archive operator to
|
|
just its root item.
|
|
|
|
|
|
<h3><a name="derivedclasses">Derived Classes</a></h3>
|
|
<p>Derived classes should include serializations of their base classes.
|
|
<pre><code>
|
|
#include <boost/serialization/base_object.hpp>
|
|
|
|
class bus_stop_corner : public bus_stop
|
|
{
|
|
friend class boost::serialization::access;
|
|
template<class Archive>
|
|
void serialize(Archive & ar, const unsigned int version)
|
|
{
|
|
// serialize base class information
|
|
ar & boost::serialization::base_object<bus_stop>(*this);
|
|
ar & street1;
|
|
ar & street2;
|
|
}
|
|
std::string street1;
|
|
std::string street2;
|
|
virtual std::string description() const
|
|
{
|
|
return street1 + " and " + street2;
|
|
}
|
|
public:
|
|
bus_stop_corner(){}
|
|
bus_stop_corner(const gps_position & lat_, const gps_position & long_,
|
|
const std::string & s1_, const std::string & s2_
|
|
) :
|
|
bus_stop(lat_, long_), street1(s1_), street2(s2_)
|
|
{}
|
|
};
|
|
</code>
|
|
</pre>
|
|
<p>
|
|
Note the serialization of the base classes from the derived
|
|
class. Do <b>NOT</b> directly call the base class serialize
|
|
functions. Doing so might seem to work but will bypass the code
|
|
that tracks instances written to storage to eliminate redundancies.
|
|
It will also bypass the writing of class version information into
|
|
the archive. For this reason, it is advisable to always make member
|
|
<code style="white-space: normal">serialize</code> functions private. The declaration
|
|
<code style="white-space: normal">friend boost::serialization::access</code> will grant to the
|
|
serialization library access to private member variables and functions.
|
|
<p>
|
|
<h3><a name="pointers">Pointers</a></h3>
|
|
Suppose we define a bus route as an array of bus stops. Given that
|
|
<ol>
|
|
<li>we might have several types of bus stops (remember bus_stop is
|
|
a base class)
|
|
<li>a given bus_stop might appear in more than one route.
|
|
</ol>
|
|
it's convenient to represent a bus route with an array of pointers
|
|
to <code style="white-space: normal">bus_stop</code>.
|
|
<pre>
|
|
<code>
|
|
class bus_route
|
|
{
|
|
friend class boost::serialization::access;
|
|
bus_stop * stops[10];
|
|
template<class Archive>
|
|
void serialize(Archive & ar, const unsigned int version)
|
|
{
|
|
int i;
|
|
for(i = 0; i < 10; ++i)
|
|
ar & stops[i];
|
|
}
|
|
public:
|
|
bus_route(){}
|
|
};
|
|
</code>
|
|
</pre>
|
|
Each member of the array <code style="white-space: normal">stops</code> will be serialized.
|
|
But, remember each member is a pointer - so what can this really
|
|
mean? The whole object of this serialization is to permit
|
|
reconstruction of the original data structures at another place
|
|
and time. In order to accomplish this with a pointer, it is
|
|
not sufficient to save the value of the pointer, rather the
|
|
object it points to must be saved. When the member is later
|
|
loaded, a new object has to be created and a new pointer has
|
|
to be loaded into the class member.
|
|
<p>
|
|
All this is accomplished automatically by the serialization
|
|
library. The above code is all that is necessary to accomplish
|
|
the saving and loading of objects accessed through pointers.
|
|
<p>
|
|
<h3><a name="arrays">Arrays</a></h3>
|
|
The above formulation is in fact more complex than necessary.
|
|
The serialization library detects when the object being
|
|
serialized is an array and emits code equivalent to the above.
|
|
So the above can be shortened to:
|
|
<pre>
|
|
<code>
|
|
class bus_route
|
|
{
|
|
friend class boost::serialization::access;
|
|
bus_stop * stops[10];
|
|
template<class Archive>
|
|
void serialize(Archive & ar, const unsigned int version)
|
|
{
|
|
ar & stops;
|
|
}
|
|
public:
|
|
bus_route(){}
|
|
};
|
|
</code>
|
|
</pre>
|
|
<h3><a name="stl">STL Collections</a></h3>
|
|
The above example uses an array of members. More likely such
|
|
an application would use an STL collection for such a purpose.
|
|
The serialization library contains code for serialization
|
|
of all STL classes. Hence, the reformulation below will
|
|
also work as one would expect.
|
|
<pre>
|
|
<code>
|
|
#include <boost/serialization/list.hpp>
|
|
|
|
class bus_route
|
|
{
|
|
friend class boost::serialization::access;
|
|
std::list<bus_stop *> stops;
|
|
template<class Archive>
|
|
void serialize(Archive & ar, const unsigned int version)
|
|
{
|
|
ar & stops;
|
|
}
|
|
public:
|
|
bus_route(){}
|
|
};
|
|
</code>
|
|
</pre>
|
|
<h3><a name="versioning">Class Versioning</a></h3>
|
|
<p>
|
|
Suppose we're satisfied with our <code style="white-space: normal">bus_route</code> class, build a program
|
|
that uses it and ship the product. Some time later, it's decided
|
|
that the program needs enhancement and the <code style="white-space: normal">bus_route</code> class is
|
|
altered to include the name of the driver of the route. So the
|
|
new version looks like:
|
|
<pre>
|
|
<code>
|
|
#include <boost/serialization/list.hpp>
|
|
#include <boost/serialization/string.hpp>
|
|
|
|
class bus_route
|
|
{
|
|
friend class boost::serialization::access;
|
|
std::list<bus_stop *> stops;
|
|
std::string driver_name;
|
|
template<class Archive>
|
|
void serialize(Archive & ar, const unsigned int version)
|
|
{
|
|
ar & driver_name;
|
|
ar & stops;
|
|
}
|
|
public:
|
|
bus_route(){}
|
|
};
|
|
</code>
|
|
</pre>
|
|
Great, we're all done. Except... what about people using our application
|
|
who now have a bunch of files created under the previous program.
|
|
How can these be used with our new program version?
|
|
<p>
|
|
In general, the serialization library stores a version number in the
|
|
archive for each class serialized. By default this version number is 0.
|
|
When the archive is loaded, the version number under which it was saved
|
|
is read. The above code can be altered to exploit this
|
|
<pre>
|
|
<code>
|
|
#include <boost/serialization/list.hpp>
|
|
#include <boost/serialization/string.hpp>
|
|
#include <boost/serialization/version.hpp>
|
|
|
|
class bus_route
|
|
{
|
|
friend class boost::serialization::access;
|
|
std::list<bus_stop *> stops;
|
|
std::string driver_name;
|
|
template<class Archive>
|
|
void serialize(Archive & ar, const unsigned int version)
|
|
{
|
|
// only save/load driver_name for newer archives
|
|
if(version > 0)
|
|
ar & driver_name;
|
|
ar & stops;
|
|
}
|
|
public:
|
|
bus_route(){}
|
|
};
|
|
|
|
BOOST_CLASS_VERSION(bus_route, 1)
|
|
</code>
|
|
</pre>
|
|
By application of versioning to each class, there is no need to
|
|
try to maintain a versioning of files. That is, a file version
|
|
is the combination of the versions of all its constituent classes.
|
|
|
|
This system permits programs to be always compatible with archives
|
|
created by all previous versions of a program with no more
|
|
effort than required by this example.
|
|
|
|
<h3><a name="splitting">Splitting <code style="white-space: normal">serialize</code>
|
|
into <code style="white-space: normal">save/load</code></a></h3>
|
|
The <code style="white-space: normal">serialize</code> function is simple, concise, and guarantees
|
|
that class members are saved and loaded in the same sequence
|
|
- the key to the serialization system. However, there are cases
|
|
where the load and save operations are not as similar as the examples
|
|
used here. For example, this could occur with a class that has evolved through
|
|
multiple versions. The above class can be reformulated as:
|
|
<pre>
|
|
<code>
|
|
#include <boost/serialization/list.hpp>
|
|
#include <boost/serialization/string.hpp>
|
|
#include <boost/serialization/version.hpp>
|
|
#include <boost/serialization/split_member.hpp>
|
|
|
|
class bus_route
|
|
{
|
|
friend class boost::serialization::access;
|
|
std::list<bus_stop *> stops;
|
|
std::string driver_name;
|
|
template<class Archive>
|
|
void save(Archive & ar, const unsigned int version) const
|
|
{
|
|
// note, version is always the latest when saving
|
|
ar & driver_name;
|
|
ar & stops;
|
|
}
|
|
template<class Archive>
|
|
void load(Archive & ar, const unsigned int version)
|
|
{
|
|
if(version > 0)
|
|
ar & driver_name;
|
|
ar & stops;
|
|
}
|
|
BOOST_SERIALIZATION_SPLIT_MEMBER()
|
|
public:
|
|
bus_route(){}
|
|
};
|
|
|
|
BOOST_CLASS_VERSION(bus_route, 1)
|
|
</code>
|
|
</pre>
|
|
The macro <code style="white-space: normal">BOOST_SERIALIZATION_SPLIT_MEMBER()</code> generates
|
|
code which invokes the <code style="white-space: normal">save</code>
|
|
or <code style="white-space: normal">load</code>
|
|
depending on whether the archive is used for saving or loading.
|
|
<h3><a name="archives">Archives</a></h3>
|
|
Our discussion here has focused on adding serialization
|
|
capability to classes. The actual rendering of the data to be serialized
|
|
is implemented in the archive class. Thus the stream of serialized
|
|
data is a product of the serialization of the class and the
|
|
archive selected. It is a key design decision that these two
|
|
components be independent. This permits any serialization specification
|
|
to be usable with any archive.
|
|
<p>
|
|
In this tutorial, we have used a particular
|
|
archive class - <code style="white-space: normal">text_oarchive</code> for saving and
|
|
<code style="white-space: normal">text_iarchive</code> for loading. There other archives
|
|
included in with the library and their interfaces are identical
|
|
(with one exception). Once serialization has been defined for
|
|
a class, that class can be serialized to any type of archive.
|
|
<p>
|
|
If the current set of archives doesn't provide one with the
|
|
attributes, format, or behavior need for a particular application,
|
|
one can either make a new one or derive from an existing one.
|
|
This is described later in the manual.
|
|
<p>
|
|
Note also that though our examples save and load the program data
|
|
to an archive within the same program, this merely a convenience
|
|
for purposes of illustration. In general, the archive may or may
|
|
not be loaded by the same program that created it.
|
|
<p>
|
|
The complete demo program - <a href="../example/demo.cpp" target="demo_cpp">demo.cpp</a>
|
|
does the following:
|
|
<ol>
|
|
<li>Creates a structure of differing kinds of stops, routes
|
|
and schedules
|
|
<li>Displays it
|
|
<li>Serializes it to a file named "testfile.txt" with one
|
|
statement
|
|
<li>Restores to another structure
|
|
<li>Displays the restored structure
|
|
</ol>
|
|
<p>
|
|
<a href="../example/demo_output.txt" target="demo_output">Output of
|
|
this program</a> is sufficient to verify that all the
|
|
originally stated requirements for a serialization system
|
|
are met with this system. The <a href="../example/demofile.txt"
|
|
target="test_file">contents of the archive file</a> can
|
|
also be displayed as serialization files are ASCII text.
|
|
<hr>
|
|
<p>Revised
|
|
<!--webbot bot="Timestamp" S-Type="EDITED" S-Format="%d %B, %Y" startspan -->
|
|
24 January, 2004
|
|
<!--webbot bot="Timestamp" endspan i-checksum="39359" -->
|
|
</p>
|
|
<p><i>© Copyright <a href="http://www.rrsd.com">Robert Ramey</a>
|
|
2002-2004. All Rights Reserved.</i></p>
|
|
</body>
|
|
</html>
|