Using XML to Save and Restore a Collection of Objects

Problem

You want to be able to save a collection of C++ objects to an XML document and read it back into memory later.

Solution

Use the Boost Serialization library. This library allows you to save and restore objects using classes called archives . To make use of this library, you must first make each of your classes serializable, which just means that instances of the class can be written to an archive, or serialized, and read back into memory, or deserialized. Then, at runtime, you can save your objects to an XML archive using the << operator and restore them using the >> operator.

To make a class serializable, add a member function template serialize with the following signature:

template
void serialize(Archive& ar, const unsigned int version);

The implementation of serialize should write each data member of the class to the specified archive as a name-value pair, using the & operator. For example, if you want to serialize and deserialize instances of the class Contact from Example 14-2, add a member function serialize, as shown in Example 14-25.

Example 14-25. Adding support for serialization to the class Contact from Example 14-2

#include  // "name-value pair"

class Contact {
...
private:
 friend class boost::serialization::access;
 template
 void serialize(Archive& ar, const unsigned int version)
 { 
 // Write (or read) each data-member as a name-value pair
 using boost::serialization::make_nvp;
 ar & make_nvp("name", name_); 
 ar & make_nvp("phone", phone_); 
 }
 ...
};

Similarly, you can make the class Animal from Example 14-2 serializable, as shown in Example 14-26.

Example 14-26. Adding support for serialization to the class Animal from Example 14-2

...
// Include serialization support for boost::gregorian::date
#include 
...
class Contact {
... 
private:
 friend class boost::serialization::access;
 template
 void serialize(Archive& ar, const unsigned int version)
 {
 // Write (or read) each data-member as a name-value pair 
 using boost::serialization::make_nvp;
 ar & make_nvp("name", name_); 
 ar & make_nvp("species", species_); 
 ar & make_nvp("dateOfBirth", dob_); 
 ar & make_nvp("veterinarian", vet_);
 ar & make_nvp("trainer", trainer_);
 }
...
};

You can now serialize an Animal by creating an XML archive of type boost::archive::xml_oarchive and writing the animal to the archive using the << operator. The xml_oarchive constructor takes a std::ostream as an argument; often this will be an output stream for writing to a file, but in general it can be a stream for writing to any type of resource. After an Animal is serialized, it can be read back into memory by constructing an XML archive of type boost::archive::xml_iarchive, connecting it to the same resource as the original archive, and invoking the >> operator.

Example 14-27 shows how to use Boost.Serialization to save a std::vector of Animals to the file animals.xml and then load it back into memory. The contents of the file animals.xml after running the program in Example 14-27 are shown in Example 14-28.

Example 14-27. Serializing a std::vector of Animals

#include 
#include  // Archive for writing XML
#include  // Archive for reading XML
#include  // machinery for serializing
#include "animal.hpp" // std::vector

int main( )
{
 using namespace std;
 using namespace boost::archive; // namespace for archives
 using namespace boost::serialization; // namespace for make_nvp

 try {
 // Populate list of animals
 vector animalList;
 animalList.push_back( 
 Animal( "Herby", "elephant", "1992-04-23", 
 Contact("Dr. Hal Brown", "(801)595-9627"),
 Contact("Bob Fisk", "(801)881-2260") ));
 animalList.push_back( 
 Animal( "Sheldon", "parrot", "1998-09-30", 
 Contact("Dr. Kevin Wilson", "(801)466-6498"),
 Contact("Eli Wendel", "(801)929-2506") ));
 animalList.push_back( 
 Animal( "Dippy", "penguin", "2001-06-08", 
 Contact("Dr. Barbara Swayne", "(801)459-7746"),
 Contact("Ben Waxman", "(801)882-3549") ));

 // Construct XML output archive and serialize list
 ofstream fout("animals.xml");
 xml_oarchive oa(fout);
 oa << make_nvp("animalList", animalList);
 fout.close( );

 // Construct XML intput archive and deserialize list
 ifstream fin("animals.xml");
 xml_iarchive ia(fin);
 vector animalListCopy;
 ia >> make_nvp("animalList", animalListCopy);
 fin.close( );

 if (animalListCopy != animalList) {
 cout << "XML serialization failed
";
 return EXIT_FAILURE;
 }

 } catch (const exception& e) {
 cout << e.what( ) << "
";
 return EXIT_FAILURE;
 }
}

 

Example 14-28. The file animals.xml after running the program from Example 14-27





 3
 
 Herby
 elephant
 
 19920423
 
 
 Dr. Hal Brown
 (801)595-9627
 
 
 Bob Fisk
 (801)881-2260
 
 
 
 Sheldon
 parrot
 
 19980930
 
 
 Dr. Kevin Wilson
 (801)466-6498
 
 
 Eli Wendel
 (801)929-2506
 
 
 
 Dippy
 penguin
 
 20010608
 
 
 Dr. Barbara Swayne
 (801)459-7746
 
 
 Ben Waxman
 (801)882-3549
 
 

 

Discussion

The Boost Serialization library provides the most comprehensive and flexible way to save and restore C++ objects. It's an extremely sophisticated framework; for example, it's able to serialize complex data structures containing cyclic references and pointers to polymorphic objects. Furthermore, the library is useful for much more than XML serialization: in addition to XML archives, it provides several types of text and binary archives. The XML and text archives are portable, meaning that data can be serialized on one system and deserialized on another; the binaries archives are nonportable but compact.

The XML documents produced by Boost.Serialization do not conform to any pre-existing specification, and the format may change in future versions of Boost. As a result, you cannot use these documents in conjunction with other C++ serialization frameworks. Nonetheless, XML serialization is useful because the serialized output is easy for humans to read and can be processed by XML processing tools.

Examples Example 14-25 and Example 14-26 demonstrate intrusive serialization : the classes Animal and Contact were modified to make them serializable. Boost.Serialization also supports nonintrusive serialization , allowing classes to be made serializable without modifying their definitions, provided that all of an object's state is accessible through its public interface. You've already seen an example of nonintrusive serialization in Example 14-27: the template std::vector is serializable, despite the fact that its definition is unmodifiable by end-users. In fact, all standard library containers are serializable; to make serialization available for a container defined in the standard header xxx, simply include the header boost/serialization/xxx.hpp. To learn more about nonintrusive serialization, consult the Boost.Serialization documentation.

Examples Example 14-25 and Example 14-26 also illustrate the dual role of the & operator: it acts like the << operator when an object is being serialized and like the >> operator when an object is being deserialized. This is convenient, because it allows serialization and deserialization to be implemented as a single function. In some cases, however, it's not appropriate to use a single function for serialization and deserialization; for those cases, Boost.Serialization provides a mechanism for splitting the serialize( ) method into separate load( ) and save( ) methods. If you need to take advantage of this feature, consult the Boost.Serialization documentation.

In Examples Example 14-25, Example 14-26, and Example 14-27, I use the function boost::serialization::make_nvp to construct name-value pairs. Boost.Serialization also provides a macro BOOST_SERIALIZATION_NVP that allows you to serialize a variable by specifying only its name. The first component of the pair will be constructed automatically by the preprocessor using the "stringizing" operator # to convert the macro parameters to string constants:

// Same as ar & make_nvp("name_", name_); 
ar & BOOST_SERIALIZATION_NVP(name_);

In the examples, I use make_nvp instead of BOOST_SERIALIZATION_NVP to give me more control over the tag names, making the contents of XML archives easier to read.

The Boost.Serialization documentation recommends that the serialize( ) method be declared private to reduce user errors when adding serialization support to classes derived from other serializable classes. To allow Boost.Serialization to call your class's serialize( ) method, you must declare the class boost::serialization::access to be a friend.

Finally, the second parameter of the serialize( ) method in Examples Example 14-25 and Example 14-26 is part of Boost.Serialization's support for class versioning. The first time an object of a certain class is saved to an archive, the class's version is also saved; when an instance of the class is deserialized, Boost.Serialization passes the stored version as the second argument to serialize. This information can be used to customize deserialization; for example, serialize might load the value of a member variable only if the class version recorded in the archive is as least as high as the first version of the class to declare that variable. By default, a class's version is 0. To specify a class's version, invoke the macro BOOST_CLASS_VERSION--defined in the header boost/serialization/version.hpppassing the name of the class and class's version as arguments.

Building C++ Applications

Code Organization

Numbers

Strings and Text

Dates and Times

Managing Data with Containers

Algorithms

Classes

Exceptions and Safety

Streams and Files

Science and Mathematics

Multithreading

Internationalization

XML

Miscellaneous

Index



C++ Cookbook
Secure Programming Cookbook for C and C++: Recipes for Cryptography, Authentication, Input Validation & More
ISBN: 0596003943
EAN: 2147483647
Year: 2006
Pages: 241

Flylib.com © 2008-2020.
If you may any questions please contact us: flylib@qtcs.net