Designing Your Class to Use Isomorphic Persistence

Isomorphic persistence classes lists the Essential Tools Module classes that implement isomorphic persistence. You can also add isomorphic persistence to an existing class, even if you only have the header files for that class. Before you can add isomorphic persistence to a class, it must meet the following requirements:

Class T must have appropriate default and copy constructors defined or generated by the compiler:

 

T(); // default constructor

T(T& t); // copy constructor

Class T must have an assignment operator defined as a member or as a global function:

 

T& operator=(const T& t); // member function

T& operator=(T& lhs, const T& rhs); // global function

On some older compilers, Class T cannot have any non-type template parameters. For example, in RWTBitVec<Size>, “size” is placeholder for a value rather than a type. The global functions used to implement isomorphic persistence (rwRestoreGuts() and rwSaveGuts()) are function templates when they are used to persist templatized classes.

Class T must use the macros RW_DECLARE_PERSISTABLE and RW_DEFINE_PERSISTABLE or their equivalents. More about this in Add RW_DECLARE_PERSISTABLE to Your Header File and Add RW_DEFINE_PERSISTABLE to One Source File.

All the data necessary to recreate an instance of Class T must be globally available (have accessor functions). If you can't make this data available, you can't implement isomorphic persistence. More about this in Make All Necessary Class Data Available.

If your class T will be stored in a C++ Standard Library container or a C++ Standard Library-based collection, you may need to implement operator<(const T&, const T&) and operator==(const T&, const T&). See Example Using the Essential Tools Module with the C++ Standard Library for more information.

To create an isomorphically persistent class or to add isomorphic persistence to an existing class, follow these steps:

1.  Make all necessary class data available.

2.  Add RW_DECLARE_PERSISTABLE to your header file.

3.  Add RW_DEFINE_PERSISTABLE to one source file.

4.  Check for possible problems.

5.  Define rwSaveGuts() and rwRestoreGuts().

Make All Necessary Class Data Available

All class data that will be isomorphically persisted must be accessible to the global functions, rwSaveGuts() and rwRestoreGuts(), used to implement persistence for the class. Note that only the information necessary to recreate an object of that class must be accessible to rwSaveGuts() and rwRestoreGuts(). Other data can be kept protected or private.

There are several ways to make protected and private data members of classes accessible. First, your class could make friends with rwSaveGuts() and rwRestoreGuts():

 

class Friendly {

// These global functions access private members.

friend void rwSaveGuts(RWvostream&, const Friendly&);

friend void rwRestoreGuts(RWFile&, Friendly&);

friend void rwSaveGuts(RWFile&, const Friendly&);

friend void rwRestoreGuts(RWvistream&, Friendly&);

//...

};

Or your class could have accessor functions to the restricted but necessary members:

 

class Accessible {

public:

int secret(){return secret_}

void secret(const int s){secret_ = s}

//...

private:

int secret_;

};

 

If you can not change the source code for the class to which you want to add isomorphic persistence, then you could consider deriving a new class that provides access via public methods or friendship:

 

class Unfriendly{

protected:

int secret_;

// ...

};

class Friendlier : public Unfriendly {

public:

int secret(){return secret_}

void secret(const int s){secret_ = s}

//...

};

 

If you can not change the source code for a class, you will be unable to isomorphically persist private members of that class. But remember: you only need access to the data necessary to recreate the class object, not to all the members of the class. For example, if your class has a private cache that is created at run time, you probably do not need to save and restore the cache. Thus, even though that cache is private, you do not need access to it in order to persist the class object.

Add RW_DECLARE_PERSISTABLE to Your Header File

Once you have determined that all necessary class data is accessible, you must add declaration statements to your header files. These statements declare the global functions operator<< and operator>> for your class. The global functions permit storage to and retrieval from RWvistream, RWvostream and RWFile.

The Essential Tools Module provides several macros that make adding these declarations easy. The macro you choose depends upon whether your class is templatized or not, and if it is templatized, how many templatized parameters it has.

For non-templatized classes, use RW_DECLARE_PERSISTABLE.

RW_DECLARE_PERSISTABLE is a macro found in rw/edefs.h . To use it, add the following lines to your header file (*.h ):

 

#include <rw/edefs.h>

RW_DECLARE_PERSISTABLE(YourClass)

RW_DECLARE_PERSISTABLE(YourClass) will expand to declare the following global functions:

 

RWvostream& operator<<(RWvostream& strm, const YourClass& item);

RWvistream& operator>>(RWvistream& strm, YourClass& obj);

RWvistream& operator>>(RWvistream& strm, YourClass*& pObj);

RWFile& operator<<(RWFile& strm, const YourClass& item);

RWFile& operator>>(RWFile& strm, YourClass& obj);

RWFile& operator>>(RWFile& strm, YourClass*& pObj);

For templatized classes with a single template parameter T, use the macro RW_DECLARE_PERSISTABLE_TEMPLATE.

RW_DECLARE_PERSISTABLE_TEMPLATE is also found in rw/edefs.h . To use it, add the following lines to your header file (*.h ):

 

#include <rw/edefs.h>

RW_DECLARE_PERSISTABLE_TEMPLATE(YourClass)

The macro RW_DECLARE_PERSISTABLE_TEMPLATE(YourClass) will expand to declare the following global functions:

 

template<class T>

RWvostream& operator<<

(RWvostream& strm, const YourClass<T>& item);

template<class T>

RWvistream& operator>>

(RWvistream& strm, YourClass<T>& obj);

template<class T>

RWvistream& operator>>

(RWvistream& strm, YourClass<T>*& pObj);

template<class T>

RWFile& operator<<(RWFile& strm, const YourClass<T>& item);

template<class T>

RWFile& operator>>(RWFile& strm, YourClass<T>& obj);

template<class T>

RWFile& operator>>(RWFile& strm, YourClass<T>*& pObj);

For templatized classes with more than one and less than five template parameters, use one of the following macros from rw/edefs.h.:

 

// For YourClass<T1,T2>:

RW_DECLARE_PERSISTABLE_TEMPLATE_2(YourClass)

// For YourClass<T1,T2,T3>:

RW_DECLARE_PERSISTABLE_TEMPLATE_3(YourClass)

// For YourClass<T1,T2,T3,T4>:

RW_DECLARE_PERSISTABLE_TEMPLATE_4(YourClass)

If you need to persist templatized classes with five or more template parameters, you can write additional macros for RW_DECLARE_PERSISTABLE_TEMPLATE_n. The macros are found in the header file rw/edefs.h.

Add RW_DEFINE_PERSISTABLE to One Source File

After you have declared the global storage and retrieval operators, you must define them. The Essential Tools Module provides macros that add code to your source file to define the global functions operator<< and operator>> for storage to and retrieval from RWvistream, RWvostream, and RWFile. RW_DEFINE_PERSISTABLE macros will automatically create global operator<< and operator>> functions that perform isomorphic persistence duties and call the global persistence functions rwSaveGuts() and rwRestoreGuts() for your class. (You may find, for template classes, that with some compilers the source file must have the same base name as the header file where RW_DECLARE_PERSISTABLE was used.) More about rwSaveGuts() and rwRestoreGuts() later.

Again, your choice of which macro to use is determined by whether your class is templatized, and if so, how many parameters it requires.

For non-templatized classes, use RW_DEFINE_PERSISTABLE.

RW_DEFINE_PERSISTABLE is a macro found in rw/epersist.h . To use it, add the following lines to one and only one source file (*.cpp or *.C):

 

#include <rw/epersist.h>

RW_DEFINE_PERSISTABLE(YourClass)

RW_DEFINE_PERSISTABLE(YourClass) will expand to generate the source code for (that is, to define) the following global functions:

 

RWvostream& operator<<(RWvostream& strm,

const YourClass& item)

RWvistream& operator>>(RWvistream& strm, YourClass& obj)

RWvistream& operator>>(RWvistream& strm, YourClass*& pObj)

RWFile& operator<<(RWFile& strm, const YourClass& item)

RWFile& operator>>(RWFile& strm, YourClass& obj)

RWFile& operator>>(RWFile& strm, YourClass*& pObj)

For templatized classes with a single template parameter T, use RW_DEFINE_PERSISTABLE_TEMPLATE.

RW_DEFINE_PERSISTABLE_TEMPLATE is also found in rw/epersist.h . To use it, add the following lines to one and only one source file (*.cpp or *.C):

 

#include <rw/epersist.h>

RW_DEFINE_PERSISTABLE_TEMPLATE(YourClass)

RW_DEFINE_PERSISTABLE_TEMPLATE(YourClass) will expand to generate the source code for the following global functions:

 

template<class T>

RWvostream& operator<<

(RWvostream& strm, const YourClass<T>& item)

 

template<class T>

RWvistream& operator>>

(RWvistream& strm, YourClass<T>& obj)

 

template<class T>

RWvistream& operator>>

(RWvistream& strm, YourClass<T>*& pObj)

 

template<class T>

RWFile& operator<<(RWFile& strm, const YourClass<T>& item)

 

template<class T>

RWFile& operator>>(RWFile& strm, YourClass<T>& obj)

 

template<class T>

RWFile& operator>>(RWFile& strm, YourClass<T>*& pObj)

For templatized classes with more than one and less than five template parameters, use one of the following macros from rw/epersist.h:

 

// For YourClass<T1,T2>:

RW_DEFINE_PERSISTABLE_TEMPLATE_2(YourClass)

// For YourClass<T1,T2,T3>:

RW_DEFINE_PERSISTABLE_TEMPLATE_3(YourClass)

// For YourClass<T1,T2,T3,T4>:

RW_DEFINE_PERSISTABLE_TEMPLATE_4(YourClass)

If you need to persist templatized classes with five or more template parameters, you can write additional macros for RW_DEFINE_PERSISTABLE_TEMPLATE_n. The macros are found in the header file rw/epersist.h.

Check for Possible Problems

You have made the necessary data accessible, and declared and defined the global functions required for isomorphic persistence. Before you go any further, you need to review your work for possible problems.

If you have defined any of the following global operators and you use the RW_DEFINE_PERSISTABLE macro, you will get compiler ambiguity errors.

 

RWvostream& operator<<(RWvostream& s, const YourClass& t);

RWvistream& operator>>(RWvistream& s, YourClass& t);

RWvistream& operator>>(RWvistream& s, YourClass*& pT);

RWFile& operator<<(RWFile& s, const YourClass& t);

RWFile& operator>>(RWFile& s, YourClass& t);

RWFile& operator>>(RWFile& s, YourClass*& pT);

The compiler errors occur because using RW_DEFINE_PERSISTABLE along with a different definition of the operators defines the operators twice. This means that the compiler does not know which operator definition to use. In this case, you have two choices:

1.  Remove the operator<< and operator>> global functions that you previously defined for YourClass and replace them with the operators generated by the RW_DEFINE_PERSISTABLE(YourClass).

2.  Modify your operator<< and operator>> global functions for YourClass using the contents of the RW_DEFINE_PERSISTABLE macro in rw/epersist.h as a guide.

Define rwSaveGuts and rwRestoreGuts

Now you must add to one and only one source file the global functions rwSaveGuts() and rwRestoreGuts(), which will be used to save and restore the internal state of your class. These functions are called by the operator<< and operator>> that were declared and defined as discussed in Add RW_DECLARE_PERSISTABLE to Your Header File and Add RW_DEFINE_PERSISTABLE to One Source File above.

Note: Writing rwSaveGuts and rwRestoreGuts Functions provides guidelines about how to write rwSaveGuts() and rwRestoreGuts() global functions.

For non-templatized classes, define the following functions:

 

void rwSaveGuts(RWFile& f, const YourClass& t){/* ...*/}

void rwSaveGuts(RWvostream& s, const YourClass& t) {/* ...*/}

void rwRestoreGuts(RWFile& f, YourClass& t) {/* ...*/}

void rwRestoreGuts(RWvistream& s, YourClass& t) {/* ...*/}

For templatized classes with a single template parameter T, define the following functions:

 

template<class T> void

rwSaveGuts(RWFile& f, const YourClass<T>& t){/* ...*/}

template<class T> void

rwSaveGuts(RWvostream& s, const YourClass<T>& t) {/* ...*/}

template<class T> void

rwRestoreGuts(RWFile& f, YourClass<T>& t) {/* ...*/}

template<class T>void

rwRestoreGuts(RWvistream& s, YourClass<T>& t) {/* ...*/}

For templatized classes with more than one template parameter, define rwRestoreGuts() and rwSaveGuts() with the appropriate number of template parameters.

Function rwSaveGuts() saves the state of each class member necessary for persistence to an RWvostream or an RWFile. If the members of your class can be persisted (see Table 2 above), and if the necessary class members are accessible to rwSaveGuts(), you can use operator<< to save the class members.

Function rwRestoreGuts() restores the state of each class member necessary for persistence from an RWvistream or an RWFile. Provided that the members of your class are types that can be persisted, and provided that the members of your class are accessible to rwRestoreGuts(), you can use operator>> to restore the class members.