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