Essential Tools Module User's Guide : Chapter 6 Collection Classes : Designing an RWCollectable Class : Bus Example Scenario
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.
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:
RW_DECLARE_ABSTRACT_COLLECTABLE_CLASS(USER_MODULE, ClassName)
RW_DEFINE_ABSTRACT_COLLECTABLE_CLASS(USER_MODULE, ClassName)
RE_DECLARE_COLLECTABLE_CLASS(USER_MODULE, ClassName)
RW-DEFINE_COLLECTABLE_CLASS_BY_ID(USER_MODULE, ClassName, Id)
RW_DEFINE_COLLECTABLE_CLASS_BY_NAME(USER_MODULE, ClassName, Str)
These macros use the following tokens as parameters:
USER_MODULE is a special token for any custom macro name that you choose. It indicates the module in which the class is built, either your library or your program. To correctly export or import symbols between your libraries, no two modules should use the same token name. This is especially important in a multiple library environment.
ClassName is the name of the class in which the macro is used.
Id is the class ID used to identify the class.
Str is the string used to identify the class.
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.
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:
 
// library internal header
// library.h
#ifdef LIBRARY_SOURCE
# define EXPORT __declspec(dllexport)
#else
# define EXPORT __declspec(dllimport)
#endif // LIBRARY_SOURCE
 
// library header, declaring exported type
// A.h
#include <library.h>
struct EXPORT A
{
// ...
};
 
// library source file
// A.cpp
#define LIBRARY_SOURCE
#include <A.h>
//...
 
// program using the above library
#include <A.h>
 
int main ()
{
A a;
}
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:
// ...
#define BUS_SCHEDULE_FUNCTION
// ...
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:
 
// user header
// my_header.h
#define MODULE_FUNCTION
 
// program source
#include <my_header.h>
class A : public RWCollectable
{
RW_DECLARE_COLLECTABLE_CLASS(MODULE, A)
// ...
};
For more information, refer to rw/rwwind.h.