Essential Tools Module User's Guide : Chapter 8 Persistence : Isomorphic Persistence : Isomorphic versus Simple Persistence
Isomorphic versus Simple Persistence
Here are two illustrations that show the difference between isomorphic and simple persistence. In Figure 2, a collection of multiple pointers to the same object is saved to and restored from a stream, using simple persistence. Notice that when the collection is stored and restored in Figure 2, each pointer points to a distinct object. Contrast this with the isomorphic persistence of the same collection, shown in Figure 3, in which all of the restored pointers point to the same object, just as they did in the original collection.
Figure 2 – Saving and restoring with simple persistence.
 
 
Figure 3 – Saving and restoring a Collection with isomorphic persistence
In Figure 4, we attempt to save and restore a circularly-linked list, using simple persistence. As shown in the figure, any attempt to use simple persistence to save a circularly-linked list results in an infinite loop.
The simple persistence mechanism creates a copy of each object that is pointed to and saves that object to a stream. But the simple persistence mechanism doesn't remember which objects it has already saved. When the simple persistence mechanism encounters a pointer, it has no way of knowing whether it has already saved that pointer's object. So in a circularly-linked list, the simple persistence mechanism saves the same objects over and over and over as the mechanism cycles through the list forever.
On the other hand, as shown in Figure 5, isomorphic persistence allows us to save the circularly-linked list. The isomorphic persistence mechanism uses a table to keep track of pointers it has saved. When the isomorphic persistence mechanism encounters a pointer to an unsaved object, it copies the object data, saves that object data—not the pointer—to the stream, then keeps track of the pointer in the save table. If the isomorphic persistence mechanism later encounters a pointer to the same object, instead of copying and saving the object data, the mechanism saves the save table's reference to the pointer.
When the isomorphic persistence mechanism restores pointers to objects from the stream, the mechanism uses a restore table to reverse the process. When the isomorphic persistence mechanism encounters a pointer to an unrestored object, it recreates the object with data from the stream, then changes the restored pointer to point to the recreated object. The mechanism keeps track of the pointer in the restore table. If the isomorphic persistence mechanism later encounters a reference to an already-restored pointer, then the mechanism looks up the reference in the restore table, and updates the restored pointer to point to the object referred to in the table.
Figure 4 – Attempt to save and restore a circularly-linked list with simple persistence
Figure 5 – Saving and restoring a circularly-linked list with isomorphic persistence
Isomorphic Persistence of an Essential Tools Module Class
The following example shows the isomorphic persistence of a templatized collection of RWCollectable integers, RWTPtrDlist<RWCollectableInt>. RWTPtrDlist is a templatized, reference-based, doubly-linked list that uses isomorphic persistence to store pointer references to values in memory.
This example uses RWCollectableInt instead of int because int uses simple persistence. By using RWCollectableInts, we can implement isomorphic persistence.
When RWTPtrDlist is saved and then restored, the pointer relationships of the restored list has the same morphology as the original list.
 
#include <assert.h>
#include <rw/tpdlist.h> // RWTPtrDlist
#include <rw/collint.h> // RWCollectableInt
#include <rw/rwfile.h> // RWFile
int main (){
RWTPtrDlist<RWCollectableInt> dlist1;
RWCollectableInt *one = new RWCollectableInt(1);
dlist1.insert(one);
dlist1.insert(one);
{
RWFile f("dlist.dat");
f << dlist1; // Isomorphic persistence of dlist1.
}
assert(dlist1[0] == one && dlist1[0] == dlist1[1]);
// dlist1[0], dlist[1] and "one" all point to the
// same place.
RWTPtrDlist<RWCollectableInt> dlist2;
{
RWFile f("dlist.dat");
f >> dlist2;
// restore dlist2 from f
// dlist2 now contains 2 pointers
// to the same RWCollectableInt of value 1.
// However, this RWCollectableInt isn't at
// the same address as the value
// that "one" points to.
}
// See the figure following this example to see what dlist1 and
// dlist2 now look like in memory.
assert(dlist2[0] == dlist2[1] && (*dlist2[0]) == *one);
// dlist2[0] and dlist2[1] point to the same place
// and that place has the same value as "one".
delete dlist2[0];
delete one;
// The developer must allocate and delete objects.
// The templatized collection member function
// clearAndDestroy() doesn't check that a given
// pointer is deleted only once.
// So in this case, delete the shared
// pointer manually.
return 0;
}
Figure 6 – After isomorphic save and restore of RWTPtrDlist<RWCollectableInt>
Designing Your Class to Use Isomorphic Persistence
Table 17 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.
Writing rwSaveGuts and rwRestoreGuts Functions
The next two sections discuss guidelines for writing rwSaveGuts() and rwRestoreGuts() global functions. To illustrate these guidelines, the following class will be used:
 
class Gut {
public:
int fundamentalType_;
size_t aSizeTValue_;
RWCString aRogueWaveObject_;
RWTValDlist anotherRogueWaveObject_;
RWCollectableString anRWCollectable_
RWCollectableString* pointerToAnRWCollectable_;
Gut* pointerToAnObject_;
};
The discussion in the next two sections describes how to write rwSaveGuts() and rwRestoreGuts() functions for non-templatized classes. However, the descriptions also apply to the templatized rwSaveGuts() and rwRestoreGuts() that are written for templatized classes.
Guidelines for Writing rwSaveGuts
The global overloaded functions:
 
rwSaveGuts(RWFile& f, const YourClass& t)
rwSaveGuts(RWvostream& s, const YourClass& t)
are responsible for saving the internal state of a YourClass object to either a binary file (using class RWFile) or to a virtual output stream (an RWvostream). This allows the object to be restored at some later time.
The rwSaveGuts() functions that you write must save the state of each member in YourClass, including the members of the class from which you inherited.
How you write the functions depends upon the type of the member data:
To save member data that are either C++ fundamental types (int, char, float, ...), or most Rogue Wave classes, including RWCollectable, use the overloaded insertion operator operator<<.
Since size_t is normally a typedef for one of the basic types, it cannot be handled by operator<< (it would cause an ambiguity error), so you will need to save it specifically by calling putSizeT(aSizeTValue);.
Saving members that are pointers to non RWCollectable objects can be a bit tricky. This is because it is possible that a pointer does not point to any object at all. One way of dealing with the possibility of nil pointers is to check whether a pointer points to a valid object. If the pointer is valid, save a boolean true, then save the dereferenced pointer. If the pointer is invalid, save a boolean false but don't save the pointer.
When you restore the pointer, rwRestoreGuts() first restores the boolean. If the boolean is true, then rwRestoreGuts() restores the valid pointer. If the boolean is false, then rwRestoreGuts() sets the pointer to nil.
Saving pointers to objects derived from RWCollectable is easier. It is still possible that a pointer is nil. But if you use:
 
RWvostream& operator<<(RWvostream&, const RWCollectable*);
to save the pointer, the nil pointer will be detected automatically.
Using these guidelines, you can write rwSaveGuts() functions for the example class Gut as follows:
 
void rwSaveGuts(RWvostream& stream, const Gut& gut) {
// Use insertion operators to save fundamental objects,
// Rogue Wave objects, and pointers to
// RWCollectable-derived objects.
stream << gut.fundamentalType_
stream.putSizeT(gut.aSizeTValue_);
stream
<< gut.aRogueWaveObject_
<< gut.anotherRogueWaveObject_
<< gut.pointerToAnRWCollectable_;
// The tricky saving of a pointer
// to a non-RWCollectable object.
if (gut.pointerToAnObject_ == 0) { // Is it a nil pointer?
stream << false; // Yes, don't save.
}else {
stream << true; // No, it's valid
stream << gut.pointerToAnObject_; // so save it.
}
}
void rwSaveGuts(RWFile& stream, const Gut& gut) {
// The body of this function is identical to
// rwSaveGuts(RWvostream& stream, const Gut& gut).
}
Guidelines for Writing rwRestoreGuts
The global overloaded functions:
 
rwRestoreGuts(RWFile& f, YourClass& t)
rwRestoreGuts(RWvistream& s, YourClass& t)
are responsible for restoring the internal state of a YourClass object from either a binary file (using class RWFile) or from a virtual input stream (an RWvistream).
The rwRestoreGuts() functions that you write must restore the state of each member in YourClass, including the members of the class that you inherited from. The functions must restore member data in the order that it was saved.
How you write the functions depends upon the type of the member data:
To restore member data that are either C++ fundamental types (int, char, float, ...) or most Rogue Wave classes, including RWCollectable, use the overloaded extraction operators (operator>>).
Since size_t is normally a typedef for one of the basic types, it cannot be handled by operator<< (it would cause an ambiguity error), so you will need to save it specifically by calling getSizeT(aSizeTValue);.
Restoring members that are pointers to non RWCollectable objects can be a bit tricky. This is because it is possible that a saved pointer did not point to any object at all. But if rwSaveGuts() saved a boolean flag before saving the pointer, as we described in the previous section, then it is a relatively simple matter for the rwRestoreGuts() to restore valid and nil pointers.
Assuming that the members were saved with a compatible rwSaveGuts(), when you restore the pointer, rwRestoreGuts() first restores the boolean. If the boolean is true, then rwRestoreGuts() restores the valid pointer. If the boolean is false, then rwRestoreGuts() sets the pointer to nil.
Restoring pointers to objects derived from RWCollectable is easier. It is still possible that the pointer is nil. But if you use:
 
RWvistream& operator>>(RWvistream&, const RWCollectable*&);
to restore the pointer, the nil pointer will be detected automatically.
Using these guidelines, you can write the rwRestoreGuts() functions for the example class Gut as follows:
 
void rwRestoreGuts(RWvistream& stream, const Gut& gut) {
// Use extraction operators to restore fundamental objects,
// Rogue Wave objects, and pointers to
// RWCollectable-derived objects.
stream >> gut.fundamentalType_
stream.getSizeT(gut.aSizetValue_);
stream
>> gut.aRogueWaveObject_
>> gut.anotherRogueWaveObject_
>> gut.pointerToAnRWCollectable_;
// The tricky restoring of a pointer
// to a non-RWCollectable object.
bool isValid;
stream >> isValid; // Is it a nil pointer?
if (isValid) // No,
stream >> gut.pointerToAnObject_; // restore the pointer.
else // Yes,
gut.pointerToAnObject_ = rwnil; // set pointer to nil.
}
void rwRestoreGuts(RWFile& stream, Gut& gut) {
// The body of this function is identical to
// rwRestoreGuts(RWvostream& stream, Gut& gut).
}
NOTE >> The order of the data is not important as long as the rwSaveGuts and rwRestoreGuts are isomorphic (See “A Few Friendly Warnings” for guidelines).
Isomorphic Persistence of a User-designed Class
“Example Two: Simple Persistence and Pointers” described some example code that implements simple persistence on a collection that includes pointers. That example illustrated how simple persistence does not maintain the original collection's morphology.
This example implements isomorphic persistence on the collection we set up in “Example Two: Simple Persistence and Pointers”: Team, which contains three Developers. Figure 7 shows the morphology of the original Team collection and of the Team collection after we saved and restored it with isomorphic persistence.
Figure 7 – Isomorphic Persistence
As you read the code, notice how the Developer::alias_ member, which points to other Developers, is saved and restored. You'll find that after saving Developer::name_, the rwSaveGuts() function for Developer checks to see if alias_ is pointing to a Developer in memory. If not, rwSaveGuts() stores a boolean false to signify that alias_ is a nil pointer. If alias_ is pointing to a Developer, rwSaveGuts() stores a boolean true. It is only afterwards that rwSaveGuts() finally stores the value of the Developer that alias_ is pointing to.
This code can distinguish between new Developers and existing Developers because the insertion operators generated by RW_DEFINE_PERSISTABLE(Developer) keep track of Developers that have been stored previously. The insertion operator, operator<<, calls the rwSaveGuts() if and only if a Developer has not yet been stored in the stream by operator<<.
When a Developer object is restored, the extraction operator, operator>>, for Developer is called. Like the insertion operators, the extraction operators are generated by RW_DEFINE_PERSISTABLE(Developer). If a Developer object has already been restored, then the extraction operator will adjust the Developer::alias_ pointer so that it points to the already existing Developer. If the Developer has not yet been restored, then rwRestoreGuts() for Developer will be called.
After restoring Developer::name_, rwRestoreGuts() for Developer restores a boolean value to determine whether Developer::alias_ should point to a Developer in memory or not. If the boolean is true, then alias_ should point to a Developer, so rwRestoreGuts() restores the Developer object. Then rwRestoreGuts() updates alias_ to point to the restored Developer.
The isomorphic persistence storage and retrieval process described above for Developer.alias_ can also be applied to the Developer pointers in Team.
Here is the code:
 
#include <iostream> // For user output.
#include <assert.h>
#include <rw/cstring.h>
#include <rw/rwfile.h>
#include <rw/epersist.h>
using namespace std;
//------------------ Declarations ---------------------
//------------------- Developer -----------------------
class Developer {
public:
Developer
(const char* name = "", Developer* anAlias = rwnil)
: name_(name), alias_(anAlias) {}
RWCString name_;
Developer* alias_;
};
 
#include <rw/edefs.h>
RW_DECLARE_PERSISTABLE(Developer)
//--------------------- Team --------------------------
class Team {
public:
Developer* member_[3];
};
RW_DECLARE_PERSISTABLE(Team);
//---------- rwSaveGuts and rwRestoreGuts -------------
//------------------- Developer -----------------------
RW_DEFINE_PERSISTABLE(Developer)
// This macro generates the following insertion and extraction
// operators:
// RWvostream& operator<<
// (RWvostream& strm, const Developer& item)
// RWvistream& operator>>(RWvistream& strm, Developer& obj)
// RWvistream& operator>>(RWvistream& strm, Developer*& pObj)
// RWFile& operator<<(RWFile& strm, const Developer& item)
// RWFile& operator>>(RWFile& strm, Developer& obj)
// RWFile& operator>>(RWFile& strm, Developer*& pObj)
void rwSaveGuts(RWFile& file, const Developer& developer){
// Called by:
// RWFile& operator<<(RWFile& strm, const Developer& item)
// file << developer.name_; // Save name.
// See if alias_ is pointing to a Developer in memory.
// If not, then rwSaveGuts stores a boolean false to signify
// that alias_ is a nil pointer. If alias_ is pointing
// to a Developer, then rwSaveGuts stores a boolean true
// and stores the value of the Developer that alias_ is
// pointing to.
if (developer.alias_ == rwnil) {
file << false; // No alias.
}
else {
file << true;
file << *(developer.alias_); // Save alias.
}
}
void rwSaveGuts(RWvostream& stream, const Developer& developer) {
// Called by:
// RWvostream& operator<<
// (RWvostream& strm, const Developer& item)
stream << developer.name_; // Save name.
// See if alias_ is pointing to a Developer in memory.
if (developer.alias_ == rwnil)
stream << false; // No alias.
else {
stream << true;
stream << *(developer.alias_); // Save alias.
}
}
void rwRestoreGuts(RWFile& file, Developer& developer) {
// Called by:
// RWFile& operator>>(RWFile& strm, Developer& obj)
file >> developer.name_; // Restore name.
// Should developer.alias_ point to a Developer?
bool alias;
file >> alias;
// If alias_ should point to a Developer ,
// then rwRestoreGuts restores the Developer object
// and then updates alias_ to point to the new Developer .
if (alias) // Yes.
// Call:
// RWFile& operator>>(RWFile& strm, Developer*& pObj)
file >> developer.alias_;
}
void rwRestoreGuts(RWvistream& stream, Developer& developer) {
// Called by:
// RWvistream& operator>>(RWvistream& strm, Developer& obj)
stream >> developer.name_; // Restore name.
// Should developer.alias_ point to a Developer?
bool alias;
stream >> alias;
if (alias) // Yes.
// Restore alias and update pointer.
// Calls:
// RWvistream& operator>>
// (RWvistream& strm, Developer*& pObj)
stream >> developer.alias_;
}
// For user output only:
ostream& operator<<(ostream& stream, const Developer& d) {
stream << d.name_
<< " at memory address: " << (void*)&d;
if (d.alias_)
stream << " has an alias at memory address: "
<< (void*)d.alias_ << " ";
else
stream << " has no alias.";
return stream;
}
//--------------------- Team -------------------------------
RW_DEFINE_PERSISTABLE(Team);
// This macro generates the following insertion and extraction
// operators:
// RWvostream& operator<<
// (RWvostream& strm, const Team& item)
// RWvistream& operator>>(RWvistream& strm, Team& obj)
// RWvistream& operator>>(RWvistream& strm, Team*& pObj)
// RWFile& operator<<(RWFile& strm, const Team& item)
// RWFile& operator>>(RWFile& strm, Team& obj)
// RWFile& operator>>(RWFile& strm, Team*& pObj)
void rwSaveGuts(RWFile& file, const Team& team){
// Called by RWFile& operator<<(RWFile& strm, const Team& item)
for (int i = 0; i < 3; i++)
// Save Developer value.
// Call:
// RWFile& operator<<
// (RWFile& strm, const Developer& item)
file << *(team.member_[i]);
}
void rwSaveGuts(RWvostream& stream, const Team& team) {
// Called by:
// RWvostream& operator<<(RWvostream& strm, const Team& item)
for (int i = 0; i < 3; i++)
// Save Developer value.
// Call:
// RWvostream& operator<<
// (RWvostream& strm, const Developer& item)
stream << *(team.member_[i]);
}
void rwRestoreGuts(RWFile& file, Team& team) {
// Called by RWFile& operator>>(RWFile& strm, Team& obj)
for (int i = 0; i < 3; i++)
// Restore Developer and update pointer.
// Call:
// RWFile& operator>>(RWFile& strm, Developer*& pObj)
file >> team.member_[i];
}
void rwRestoreGuts(RWvistream& stream, Team& team) {
// Called by:
// RWvistream& operator>>(RWvistream& strm, Team& obj)
for (int i = 0; i < 3; i++)
// Restore Developer and update pointer.
// Call:
// RWvistream& operator>>
// (RWvistream& strm, Developer*& pObj)
stream >> team.member_[i];
}
// For user output only:
ostream& operator<<(ostream& stream, const Team& t) {
for (int i = 0; i < 3; i++)
stream << "[" << i << "]:" << *(t.member_[i]) << endl;
return stream;
}
 
//-------------------- main --------------------------
int main (){
Developer* kevin = new Developer("Kevin");
Developer* rudi = new Developer("Rudi", kevin);
Team team1;
team1.member_[0] = rudi;
team1.member_[1] = rudi;
team1.member_[2] = kevin;
cout << "team1 (before save):" << endl
<< team1 << endl << endl; // Output to user.
{
RWFile f("team.dat");
f << team1; // Isomorphic persistence of team.
}
Team team2;
{
RWFile f("team.dat");
f >> team2;
}
cout << "team2 (after restore):" << endl
<< team2 << endl << endl; // Output to user.
delete kevin;
delete rudi;
return 0;
}
Output:
 
team1 (before save):
[0]:Rudi at memory address: 0x10002be0
has an alias at memory address: 0x10002bd0
[1]:Rudi at memory address: 0x10002be0
has an alias at memory address: 0x10002bd0
[2]:Kevin at memory address: 0x10002bd0 has no alias.
team2 (after restore):
[0]:Rudi at memory address: 0x10002c00
has an alias at memory address: 0x10002c10
[1]:Rudi at memory address: 0x10002c00
has an alias at memory address: 0x10002c10
[2]:Kevin at memory address: 0x10002c10 has no alias.