SourcePro Core : Essential Tools Module User's Guide : Advanced Topics : More on Storing and Retrieving RWCollectable Objects
More on Storing and Retrieving RWCollectable Objects
In Operators, we saw how to save and restore the morphology or pointer relationships of a class using the following global functions:
 
Rwvostream& operator<<(RWvostream&, const RWCollectable&);
RWFile& operator<<(RWFile&, const RWCollectable&);
Rwvostream& operator<<(RWvostream&, const RWCollectable*);
RWFile& operator<<(RWFile&, const RWCollectable*);
Rwvistream& operator>>(RWvistream&, RWCollectable&);
RWFile& operator>>(RWFile&, RWCollectable&);
Rwvistream& operator>>(RWvistream&, RWCollectable*&);
RWFile& operator>>(RWFile&, RWCollectable*&);
When working with RWCollectables, it is useful to understand how these functions work. Here is a brief description.
When you call one of the left-shift << operators for any collectable object for the first time, an identity dictionary is created internally. The object's address is put into the dictionary, along with its ordinal position in the output file—for example, first, second, sixth, etc.
Once this is done, a call is made to the object's virtual function saveGuts(). Because this is a virtual function, the call will go to the definition of saveGuts() used by the derived class. As we have seen, the job of saveGuts() is to store the internal components of the object. If the object contains other objects inheriting from RWCollectable, the object's saveGuts() calls operator<<() recursively for each of these objects.
Subsequent invocations of operator<<() do not create a new identity dictionary, but store the object's address in the already existing dictionary. If an address is encountered which is identical to a previously written object's address, then saveGuts() is not called. Instead, a reference is written that this object is identical to some previous object.
When the entire collection is traversed and the initial call to saveGuts() returns, the identity dictionary is deleted and the initial call to operator<<() returns.
The function operator>>() essentially reverses this whole process by calling restoreGuts() to restore objects into memory from a stream or file. When encountering a reference to an object that has already been created, it merely returns the address of the old object rather than asking the RWFactory to create a new one.
Here is a more sophisticated example of a class that uses these features:
 
#include <rw/collect.h>
#include <rw/rwfile.h>
#include <assert.h>
class Tangle : public RWCollectable
{
public:
RW_DECLARE_COLLECTABLE_CLASS(USER_MODULE, Tangle)
Tangle* nextTangle;
int someData;
Tangle(Tangle* t = 0, int dat = 0){nextTangle=t; someData=dat;}
virtual void saveGuts(RWFile&) const;
virtual void restoreGuts(RWFile&);
};
void Tangle::saveGuts(RWFile& file) const{
RWCollectable::saveGuts(file); // Save the base class
file.Write(someData); // Save internals
file << nextTangle; // Save the next link
}
void Tangle::restoreGuts(RWFile& file){
RWCollectable::restoreGuts(file); // Restore the base class
file.Read(someData); // Restore internals
file >> nextTangle; // Restore the next link
}
// Checks the integrity of a null terminated list with head "p":
void checkList(Tangle* p){
int i=0;
while (p)
{
assert(p->someData==i);
p = p->nextTangle;
i++;
}
}
RW_DEFINE_COLLECTABLE_CLASS_BY_ID(USER_MODULE, Tangle, 100)
int main(){
Tangle *head = 0, *head2 = 0;
for (int i=9; i >= 0; i--)
head = new Tangle(head,i);
checkList(head); // Check the original list
{
RWFile file("junk.dat");
file << head;
}
RWFile file2("junk.dat");
file2 >> head2;
checkList(head2); // Check the restored list
return 0;
}
In the above example, the class Tangle implements a circularly linked list. What happens? When function operator<<() is called for the first time for an instance of Tangle, it sets up the identity dictionary as described above, then calls Tangle's saveGuts(), whose definition is shown above. This definition stores any member data of Tangle, then calls operator<<() for the next link. This recursion continues on around the chain.
If the chain ends with a nil object (that is, if nextTangle is zero), then operator<<() notes this internally and stops the recursion.
On the other hand, if the list is circular, then a call to operator<<() is eventually made again for the first instance of Tangle, the one that started this whole chain. When this happens, operator<<() will recognize that it has already seen this instance before and, rather than call saveGuts() again, will just make a reference to the previously written link. This stops the series of recursive calls and the stack unwinds.
Restoration of the chain is done in a similar manner. A call to:
 
RWFile& operator>>(RWFile&, RWCollectable*&);
can create a new object off the heap and return a pointer to it, return the address of a previously read object, or return the null pointer. In the last two cases, the recursion stops and the stack unwinds.