Rogue Wave banner
Previous fileTop of DocumentContentsIndex pageNext file
Essential Tools Module User's Guide

9.6 Polymorphic Persistence

Polymorphic persistence preserves pointer relationships (or morphology) among persisted objects, and also allows the restoring process to restore an object without prior knowledge of that object's type.

The Essential Tools Module uses classes derived from RWCollectable to do polymorphic persistence. The objects created from those classes may be any of the different types derived from RWCollectable. A group of such objects, where the objects may have different types, is called a heterogeneous collection.

Table 20 lists the classes that use polymorphic persistence.

Table 20: Polymorphic persistence classes 

Category Description

RWCollectable (Smalltalk-like) classes

RWCollectableDate, RWCollectableString...

RWCollection classes (which derive from RWCollectable)

RWBinaryTree, RWBag ...

9.6.1 Operators

The storage and retrieval of polymorphic objects that inherit from RWCollectable is a powerful and adaptable feature of the The Essential Tools Module. Like other persistence mechanisms, polymorphic persistence uses the overloaded extraction and insertion operators (operator<< and operator>>). When these operators are used in polymorphic persistence, not only are objects isomorphically saved and restored, but objects of unknown type can be restored.

Polymorphic persistence uses the operators listed below.

9.6.2 Designing your Class to Use Polymorphic Persistence

Note that the ability to restore the pointer relationships of a polymorphic object is a property of the base class, RWCollectable. Polymorphic persistence can be used by any object that inherits from RWCollectable including your own classes. Section 6.16 describes how to implement polymorphic persistence in the classes that you create by inheriting from RWCollectable.

9.6.3 Polymorphic Persistence Example

This example of polymorphic persistence contains two distinct programs. The first example polymorphically saves the contents of a collection to standard output (stdout). The second example polymorphically restores the contents of the saved collection from standard input (stdin). We divided the example to demonstrate that you can use persistence to share objects between two different processes.

If you compile and run the first example, the output is an object as it would be stored to a file. However, you can pipe the output of the first example into the second example:

9.6.3.1 Example One: Saving Polymorphically

This example constructs an empty collection, inserts objects into that collection, then saves the collection polymorphically to standard output. Notice that example one creates and saves a collection that includes two copies of the same object and two other objects. The four objects have three different types. When example one saves the collection and when example two restores the collection, we see that:

Here is the first example:

Note that there are three types of objects stored in collection, an RWCollectableDate, an RWCollectableInt, and two RWCollectableStrings. The same RWCollectableString, george, is inserted into collection twice.

9.6.3.2 Example Two: Restoring Polymorphically

The second example shows how the polymorphically saved collection of the first example can be read back in and faithfully restored using the overloaded extraction operator:

In this example, persistence happens when the program executes the statement:

This statement uses the overloaded extraction operator to isomorphically restore the collection saved by the first example into collection2.

How does persistence happen? For each pointer to an RWCollectable-derived object restored into collection2 from the input stream istr, the extraction operator operator>> calls a variety of overloaded extraction operators and persistence functions. For each RWCollectable-derived object pointer, collection2's extraction operators:

We will look at the implementation details for the persistence mechanism again in Section 9.6.3.3. You should note, however, that when a heterogeneous collection (which must be based on RWCollection) is restored, the restoring process does not know the types of objects it will be restoring. Hence, it must always allocate the objects off the heap. This means that you are responsible for deleting the restored contents. This happens at the end of the example, in the expression collection2.clearAndDestroy.

Here is the listing of the example:

Program Output:

Figure 10 illustrates the collection created in the first example and restored in the second. Notice that both the memory map and the data types are identical in the saved and restored collection.

Figure 10: Polymorphic persistence

9.6.3.3 Example Two Revisited

It is worth looking at the second example again so that you can see the mechanisms used to implement polymorphic persistence. The expression:

calls the overloaded extraction operator:

This extraction operator has been written to call the object's restoreGuts() virtual function. In this case the object, obj, is an ordered collection and its version of restoreGuts() has been written to repeatedly call:

once for each member of the collection. Actually, the Smalltalk collection classes are so similar that they all share the same version of restoreGuts(). Note that its second argument is a reference to a pointer, not just a reference. This version of the overloaded operator>> looks at the stream, figures out the kind of object on the stream, allocates an object of that type off the heap, restores it from the stream, and finally returns a pointer to it. If this operator>> encounters a reference to a previous object, it just returns the old address. These pointers are inserted into the collection by the ordered collection's restoreGuts().

These details about the polymorphic persistence mechanism are particularly important when you design your own polymorphically persistable class, as described in Section 6.16. And when working with such classes, note that when Smalltalk-like collection classes are restored, the type of the restored objects is never known. Hence, the restoring processes must always allocate those objects off the heap. This means that you are responsible for deleting the restored contents. An example of this occurs at the end of both polymorphic persistence examples.

9.6.3.4 Choosing Which Persistence Operator to Use

In the second example, the persistence operator restored our collection to a reference to an RWCollectable:

instead of to a pointer to a reference to an RWCollectable:

The collection was allocated on the stack:

instead of having operator>>(RWvistream&,RWCollectable*&) allocate the memory for the collection:

Why make this choice? If you know the type of the collection you are restoring, then you are usually better off allocating it yourself, then restoring via:

By using the reference operator, you eliminate the time required for the persistence machinery to figure out the type of object and have RWFactory allocate one (see Section 6.17.7, "A Note on the RWFactory,"). Furthermore, by allocating the collection yourself, you can tailor the allocation to suit your needs. For example, you can decide to set an initial capacity for a collection class.



Previous fileTop of DocumentContentsIndex pageNext file

©2004 Copyright Quovadx, Inc. All Rights Reserved.
Rogue Wave and SourcePro are registered trademarks of Quovadx, Inc. in the United States and other countries. All other trademarks are the property of their respective owners.
Contact Rogue Wave about documentation or support issues.