Rogue Wave banner
Previous fileTop of DocumentContentsIndex pageNext file
Essential Tools Module User's Guide
Rogue Wave web site:  Home Page  |  Main Documentation Page

6.16 Designing an RWCollectable Class

Classes that derive from RWCollectable give you much of the functionality of such Smalltalk namesakes as Bag and SortedCollection, along with some of the strengths and weaknesses of C++.

The two greatest advantages are that they can be used by the Smalltalk-like collections, which also derive from RWCollectable, and that they are the only set of collection classes able to use the powerful polymorphic persistence machinery. Their biggest disadvantages are their relative lack of type-safety, and their relatively large object code size. Large code is typical even when these classes are used in only small doses because of their initially high overhead in code machinery.

Before we get to the nuts and bolts of how to design an RWCollectable class, let's discuss a concrete example of why you might choose to design RWCollectable classes.

6.16.1 Bus Example Scenario

Suppose you run a bus company. To automate part of your ridership tracking system, you want to write classes that represent a bus, its set of customers, and its set of actual passengers. In order to be a passenger, a person must be a customer. Hence, the set of customers is a superset of the set of passengers. Also, a person can physically be on the bus only once, and there is no point in putting the same person on the customer list more than once. As the developer of this system, you must make sure there are no duplicates on either list.

These duplicates can be a problem. Suppose that the program needs to be able to save and restore information about the bus and its customers. When it comes time to polymorphically save the bus, if your program naively iterates over the set of customers, then over the set of passengers, saving each one, any person who is both a customer and a passenger is saved twice. When the program polymorphically restores the bus, the list of passengers will not simply refer to people already on the customer list. Instead, each passenger has a separate instantiation on both lists.

You need some way of recognizing when a person has already been polymorphically saved to the stream and, instead of saving him or her again, merely saving a reference to the previous instance.

We will be designing the class hierarchy around the RWCollectable class and will derive our own classes from it. RWCollectable contains the necessary serialization interface.

6.16.1.1 Serialization Support Macros

The Essential Tools Module includes a number of macros which you must use to conveniently add serialization support to your classes. These macros expand to override the implementation of the RWCollectable interface functions to suit the needs of your classes.

Serialization is supported by the following set of macros defined in RWCollectable:

These macros use the following tokens as parameters:

The only limitation on the naming of Id and Str is that each must be unique since they are used to identify a particular serializable type.

When you are creating a library that defines serialization types, you must ensure that the serialization support symbols introduced by the expansion of the macros are properly exported. In addition, the symbols must be properly imported into the programs that are built on top of the library.

6.16.1.2 Exporting Symbols in a Windows Environment

In a Microsoft Windows environment, the dynamic libraries require a special inline syntax to declare a symbol as exported. The classic mechanism for exporting a specific symbol from a library and importing it into a program that uses that library is a conditionally defined macro which expands to the appropriate storage-class attributes. This macro will expand one way in the library and another way in the program. For example:

From this code example, you can see how the EXPORT macro is expanded in the library code different from the way it is expanded in the program code.

The Essential Tools Module serialization macros use a similar mechanism. To properly export the serialization symbols:

  1. In one of the internal headers of the library, define a macro such as MODULE_FUNCTION which will expand to either dllexport or dllimport.

      // user library internal header
      // library.h
      #ifdef LIBRARY_SOURCE
      #  define MODULE_FUNCTION __declspec(dllexport)
      #  define EXPORT          __declspec(dllexport)
      #else
      #  define MODULE_FUNCTION __declspec(dllimport)
      #  define EXPORT          __declspec(dllimport)
      #endif // LIBRARY_SOURCE
      
  2. The prefix of the MODULE_FUNCTION macro is then used consistently across the library as the first parameter of the serialization macros.

      // user library header
      // A.h
      class EXPORT A : public RWCollectable
      {
           // use serialization macros; pass as first parameter the 
           // prefix of the MODULE_FUNCTION macro above,
           // i.e. "MODULE" 
           RW_DECLARE_COLLECTABLE_CLASS(MODULE, A)
      
           // ...
      };
      
      // user library source files
      // A.cpp
      #define LIBRARY_SOURCE
      #include <A.h>
      
      // use serialization macros; first parameter same as above;
      // use the name "MODULE" consistently across your whole
      // library
      

For example, in a user application called "BusSchedule" you might choose a prefix such as BUS_SCHEDULE and compose the support macro as:

For a static library on a Windows platform, a static or dynamic library on any other platform, or for your application, simply define MODULE_FUNCTION with no body such as:

For more information, refer to rw/rwwind.h.

6.16.2 The Role of RWCollectable

Objects that inherit from RWCollectable have the ability to save not only their contents, but also their relationships with other objects that inherit from RWCollectable. We call this feature isomorphic persistence. Class RWCollectable has isomorphic persistence, but more than that, it can determine at run time the type of the object to be saved or restored. We call the type of persistence provided by RWCollectable polymorphic persistence, and recognize it as a superset of isomorphic persistence.

Polymorphic persistence is the storage and retrieval of objects to and from a stream or file in such a way that pointer relationships are preserved among persisted objects, which can be restored without the restoring process knowing the object's type. Polymorphic, isomorphic, and simple persistence are all covered in detail in Chapter 8, "Persistence."

6.16.3 Bus Example Code

The code below shows how we might declare the classes described in the previous section. Later we will use the macro RW_DECLARE_COLLECTABLE_CLASS and discuss our function choices. You will find the complete code from which this example is taken at the end of this chapter; it is also given as the bus example in the buildspace\examples\tools directory.

Note how both classes inherit from RWCollectable. We have chosen to implement the set of customers by using class RWSet, which does not allow duplicate entries. This will guarantee that the same person is not entered into the customer list more than once. For the same reason, we have also chosen to implement the set of passengers using class RWSet. However, we have chosen to have this set live on the heap. This will help illustrate some points in the coming discussion.



Previous fileTop of DocumentContentsIndex pageNext file

Copyright © Rogue Wave Software, Inc. All Rights Reserved.

The Rogue Wave name and logo, and SourcePro, are registered trademarks of Rogue Wave Software. All other trademarks are the property of their respective owners.
Contact Rogue Wave about documentation or support issues.