SourcePro : Essential Tools Module User's Guide : Persistence : Isomorphic Persistence : Isomorphic Persistence of a User-designed Class
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.