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

6.4 Basic Examples

This section presents simple examples that illustrate the most common uses of object serialization. The earlier examples emphasize the intrusive method. External serialization support is similar (see Section 6.4.6).


All code in this section comes from example source code distributed as part of the product. The source file appears in parentheses before the code sample.

6.4.1 Saving and Restoring an Object by Value

First, add serialization support. For example, suppose you start with an instance of class real_property:

You need to make two changes to add serialization support.

6.4.1.1 First Change

Insert the RW_DECLARE_VIRTUAL_STREAM_FNS() macro into the class declaration. The name of class must be provided as a parameter. Any declarations that appear below this macro are private. The following example shows the header file for real_property with the macro inserted.

//examples\serial\simple\real_property.h

//1

This macro declares streaming operators for the real_property class.

//2

Place the macro before anything else in the class. The macro assumes it is in a private scope, so it reinstates private as its last statement. If this public statement were ahead of the macro, the following constructors would be declared private.

//3

Object streaming requires a public default constructor.

6.4.1.2 Second Change

Define the streamContents() function for this class. Some of the macros that do this are:

Using these macros provides a built-in symmetry between the input and the output operations on the class. The macros defining the streamContents() function can be placed in any module, but are normally put in the same source file as the other definitions for that class. The following example from shows what it looks like for the real_property class.

//examples\serial\simple\real_property.cpp

//1

This macro begins the definition of the streamContents() function.

//2

This macro defines the code for inserting and extracting data to and from the address_ data member of the real_property class. The first parameter to the macro is an attribute label and need not match any name in the class, but the second parameter must be the actual name of the member that you want to stream. The macro is used again on the next line to accomplish the same thing for the size_ member.

//3

The streamContents() function is closed out with this macro.

Now that these macros have been added to real_property, this class can be written to and read from object streams using the operators operator>>(RWObjectInputStream&, real_property&) and operator<<(RWObjectOutputStream&, const real_property&). The following code shows how an instance of class real_property can be written to a file and read back.

//examples\serial\simple\real_estate.cpp

//1

Create a real_property object.

//2

Create a local scope so the output file closes automatically when it's no longer needed.

//3

Open a file for output.

//4

Create a data stream using the standard file stream just opened and then create a compact object output stream from the data stream. The second parameter indicates the kind of object that will serve as the root element. It can be one of: objectTag, sequenceTag, or mapTag.

//5

Stream out the contents of real1 to the file in compact format.

//6

Now open the file for input.

//7

Create a data stream and then create a compact object input stream from the data stream.

//8

Read the data from the first variable (real1) into the second (real2).

6.4.2 Saving and Restoring an Object by Pointer

The previous example supports only streaming objects as values. To support streaming objects as pointers requires additional changes.

6.4.2.1 First Change

Include the macros: RW_DECLARE_STREAMABLE_AS_SELF(), RW_DECLARE_STREMABALE_AS_BASE() (if your class needs to be streamed through pointers to base classes) and RW_DECLARE_STREAMABLE_POINTER() in this order in the header file for each class that is to be streamed as a pointer type. You will also have to include the RW_DECLARE_VIRTUAL_STREAM_FNS() macro described above inside the declaration of your class.


The call can go in any header file, as long as it's included before code that streams this class. Putting it in the header with the class declaration is the easiest way.

The following code shows real_property with this macro added.

//examples\serial\simple\real_property.h

//1

Place these macros after the class declaration.

The first macro declares the class' factory (which allows objects to be created on the fly from the stream) when streaming objects through pointers to this class.

The second macro declares the operators operator>>(RWObjectInputStream&, real_property*&) and operator<<(RWObjectOutputStream&, const real_property*). These allow a pointer to a real_property to be written out, and a new real_property instance to be automatically created when the stream is read back in.

6.4.2.2 Second Change

Place RW_DEFINE_STREAMABLE_AS_SELF() and RW_DEFINE_STREAMABLE_POINTER() and RW_DEFINE_STREAMABLE_AS_SELF() macros in the source file which implements the rest of the class. These macros would normally go in the same source file as the streamContents() defining macros, as shown in the following code:

//examples\serial\simple\real_property.cpp

6.4.2.3 Third Change

After adding these macros, use the new pointer-based operators to write a real_property pointer to an object stream, and then read it back. Note that the serialization factory has constructed a new real_property instance that is now owned by the module calling the input operator. The following is an example of writing a real_property pointer into a file and then reading it back again.

//examples\serial\simple\real_estate2.cpp

//1

Create a data stream using the standard file stream just opened and then create a compact object output stream from the data stream. The second parameter indicates the kind of object that will serve as the root element.

//2

Stream out the real_property object by reference.

//3

Open the file for input.

//4

Create a data stream and then create a compact object input stream from the data stream.

//5

Read the data back in to a fresh real_property object. The input stream allocates and constructs a new real_property object as part of the extraction process. Any address held by real2 prior to that operation is lost.

6.4.2.4 A Caution on Using Streaming Pointers

Take special caution when dealing with streaming pointers. The Serial package cannot determine what kind of memory a pointer points to, and cannot determine when, or if, it is safe to delete that memory. For this reason, you need to manually manage the memory associated with pointers before streaming into them.


The Serial package does not manage the memory associated with streaming pointers. To avoid a memory leak, you must manually manage the memory associated with pointers before and after streaming into them.

In particular, before streaming into a pointer, ensure that the pointer is not the only reference to a heap-allocated block of memory. If it is, the pointer value will be overwritten when the serialized pointer is streamed in, and the only reference to the heap memory will be lost. This will result in a memory leak as well as other potentially unexpected behavior.

After the pointer object has been streamed in, ensure that any memory allocated by the stream is deallocated appropriately. A streamed null pointer will set the pointer's value to 0 when it is streamed in.

6.4.3 Associations with Other Objects

In the previous examples, the object state that is streamed out and restored consists of simple attributes. An attribute is a data member that is either a primitive type (such as int or float) or a simple concrete type that is treated like a primitive type (such as RWCString or RWDate).

In real object systems, an object's state often includes associations with other objects. In C++ these associations can take several forms. The following examples show how to provide serialization support for these various forms. As long as all other classes provide support for serialization, then associations are treated just like simple attributes.

6.4.3.1 Data Member is an Object

The simplest way to associate one object with another is to include an instance of one class as a data member of the other. For instance, every piece of real_property has one and only one address. If address doesn't need to exist outside of real_property and it doesn't need to be specialized, then it makes sense for real_property to have a data member of type address. An RWCString attribute is handled in a comparable way, except that serialization support must also be added for address.

6.4.3.2 Data Member is a Pointer to an Object

Another way to associate one object with another is to store in one object a pointer to the other object. This differs from including an instance in that the pointer might be null, and the type of the pointer might be different than the actual type of object to which it points (polymorphism). Data members of pointer types that have insertion and extraction operators for object streams behave the same as simple types. For example, real_property can have more than one type of heating_system, such as baseboard or forced_air.

6.4.3.3 Data Member is a Value-based or Pointer-based Collection

Associations can also be of the one-to-many type in which an object is associated with some set of other objects of like type. Just like one-to-one associations, one-to-many associations can either store objects directly or can store pointers to objects. For example, a residential_property might contain a number of rooms (all of the same class), and an agent might list several instances of real_property (of different types).

The examples below show how these associations can be serialized if they are implemented using a value-based and pointer-based collection.

The next example uses a polymorphic attribute in the real_property class. The first two code listings show the new header and source files, respectively. The real_property class has been changed from the previous section to include a heater attribute. This attribute is a pointer to a heating_system type, which is a base class. Generally, the attribute holds either a pointer to either a baseboard or a forced_air object (see examples\serial\association\heating.h).

//examples\serial\association\real_property.h

//1

A pointer has been added to the base class heating_system. This attribute can hold a pointer to either of the derived classes (baseboard and forced_air) as shown in examples\serial\association\heating.h.

The following code sample is from: examples\serial\association\real_property.cpp.

//1

Code has been added to handle the heater attribute.

Next is the one-to-many value-based association. The residential class is changed from the previous section to include a list of rooms. The room class is itself a nested class.

//examples\serial\association\residential.h

//1

Use this macro to get streaming operators for RWTValSlist.

//2

Because the class room is nested, it must be treated specially later.

//3

Declare the streamContents() function as with any other class.

//4

This function allows you to add a room to the list of rooms held by the residential property. You can build up a list of rooms by calling this function repeatedly.

//5

The list of rooms. The framework provides inserters and extractors for Rogue Wave collection classes, so nothing extra is required.

//6

This typedef is necessary because the macros used to define the object streaming mechanism cannot deal with things like the scope resolution operator. See Section 6.4.7, "External Serialization of Templates and Nested Classes," for more details.

//7

Again for the nested class (using the typedef).

//examples\serial\association\residential.cpp

//1

The necessary macro invocation has been added to deal with our new rooms attribute.

//2

The definition of the streamContents() function has been added for the nested class type residential::room. Note the required use of the typedef from the header file.

The next example uses a one-to-many pointer-based association. An agent class is defined that holds a list of properties. Because the properties may be any one of residential, rural, or rural_land, the list is a pointer-based polymorphic collection.

First, define the agent class.

//examples\serial\association\agent.h

//1

A pointer-based collection of real_property.

Next, define the streamContents() function. Just as with RWTValSlist<T>, the framework provides an inserter and extractor for the RWTPtrSlist<T> class, so all that's needed is to use the macro RW_DECLARE_STREAMABLE_PTR_SEQUENCE(RWTPtrSlist).

//examples\serial\association\agent.cpp.

The following shows the new classes in action.

//examples\serial\association\listing.cpp

//1

Create several objects derived from real_property.

//2

Add some rooms.

//3

Create an agent object and populate its listing with properties.

//4

Add the same listing twice to demonstrate the isomorphic properties of object streaming.

//5

Create another agent and populate it by reading in from the file.

6.4.4 Templates

The Serialization package can stream objects of template classes and ordinary classes. It can also stream a template class that uses a base class pointer (polymorphism and templates). Although template classes don't often inherit from a base class, this technique is useful for situations such as storing a mixed set of primitive types together in a collection.

Serialization support for templates requires use of special macros RW_DEFINE_STREAMABLE_TEMPLATE_POINTER() and RW_DEFINE_STREAMABLE_TEMPLATE_AS_SELF() because the normal RW_DEFINE_STREAMABLE_POINTER() and RW_DEFINE_STREAMABLE_AS_SELF() macros don't work with template classes. Calls to the RW_DEFINE_STREAMABLE_AS_BASE() macro must be placed in a source file for each type of pointer that will be used to stream an object of that template class.

The RW_DEFINE_STREAMABLE_TEMPLATE_POINTER() and RW_DEFINE_STREAMABLE_TEMPLATE_AS_SELF() macros cannot be called with a template class directly; they must be called using typedefs for specific instantiations of the template. At present, only templates with a single parameter are supported, and the macro uses the name T for that parameter.

In the next example, a residential property has one or more pet_door objects parameterized by the type of pet: t_pet_door<class kind_of_pet>.

//examples\serial\template\pet_door.h

//1

Base class for collecting the template specializations.

//2

Declare the stream contents function for the template.

//3

cat and monkey classes used to specialize the t_pet_door template.

//4

These typedefs are needed for each specialization of the t_pet_door template. These could reside in pet_door.cpp also.

//5

The RW_DEFINE_STREAMABLE_TEMPLATE_POINTER macro.

//6

The template version of a streamContents() function. Note its presence in The following source code for pet_door is from: examples\serial\template\pet_door.cpp.

    RW_BEGIN_STREAM_CONTENTS(pet_door)                             // 7
    RW_END_STREAM_CONTENTS
    
    // Allow object streaming of pet_door class
    RW_DEFINE_STREAMABLE_AS_SELF(pet_door)
    RW_DEFINE_STREAMABLE_POINTER(pet_door)
    
    // Allow template specialization streaming by reference
    RW_DEFINE_STREAMABLE_AS_SELF(cat_door)                         // 8
    RW_DEFINE_STREAMABLE_AS_BASE(cat_door,pet_door)
    RW_DEFINE_STREAMABLE_POINTER(cat_door)
    
    RW_DEFINE_STREAMABLE_AS_SELF(monkey_door)
    RW_DEFINE_STREAMABLE_AS_BASE(monkey_door,pet_door)
    RW_DEFINE_STREAMABLE_POINTER(monkey_door)
    
    
//7

The streamContents() function for the non-template base class is written in the usual way and put in the usual place.

//8

Typedefs from pet_door.h support serialization by referencing the template instantiations.

6.4.5 Default Constructors

Reading objects from object streams using polymorphic pointers requires an object factory that can use new() to create an object of the correct derived type. This factory in turn needs access to the default constructor (no arguments) for each possible derived type.

This is not a problem if the default constructor is public, but when the result of calling the default constructor is an incomplete object, a protected or private constructor is desired. The RW_DECLARE_FRIEND_CTOR_METHOD() macro is included for this situation. After the call to RW_DECLARE_VIRTUAL_STREAM_FNS() within the class declaration, a call to RW_DECLARE_FRIEND_CTOR_METHOD() must be made for each base class whose factory calls this class's default constructor. The following is the header for residential after making the default constructor private.

//examples\serial\other\residential.h

//1

Use of the macro to declare the factory a friend.

//2

Note that the default constructor is private.

You cannot add a friend to an existing class without modifying its header file. Therefore when using a third party library, an instance must be created using a public constructor.

6.4.6 External Serialization

In the following examples, all classes that have been defined in the previous examples are now being provided as part of a third party library that must be used without any modifications. This library does not have support for object serialization.

To provide serialization support for the classes in this library, you use the external serialization macros, which don't require any changes to the existing class library.

Instead of inserting RW_DECLARE_VIRTUAL_STREAM_FNS() into the class declaration of real_property in the real_property.h header file, macro RW_DECLARE_EXTERNAL_STREAM_FNS() is placed in a new header file, such as real_property_ext.h. The macros RW_DECLARE_EXTERNAL_STREAMABLE_POINTER() and RW_DECLARE_EXTERNAL_STREAMABLE_AS_SELF() (and possibly RW_DECLARE_EXTERNAL_STREAMABLE_AS_BASE() if streaming through base class pointers) is also placed in this new file. This new header file needs to be included in any code that streams real_property objects.

In the next example, the real_property class is modified slightly to represent something you'd be more likely to see in the real world. It allows access to data members via public accessor functions.

//examples\serial\external\real_property.h

//1

At least one virtual function is needed, because the by-reference streaming facility relies on RTTI and that requires a v-table. A virtual streamContents() function cannot be used when using external streaming, so the somewhat contrived printMe() function is used instead. Your classes will normally handle this as part of their interface.

//2

These accessor functions enable you to serialize the data members.

What follows is the new header from examples\serial\external\real_property_ext.h, which provides for the external serialization version of the class. It is the entire contents of the header, except for include guards and such.

//1

Include the header to get the serialization macros.

//2

Include the header of the class to be serialized.

//3

Use the RW_DECLARE_EXTERNAL_STREAM_FNS macro and place it in this new file along with the RW_DECLARE_STREAMABLE_POINTER macro.

The macros calls that are normally put into an object's source file, such as real_property.cpp, must be replaced by calls in a separate source file, such as real_property_ext.cpp. The macro RW_DEFINE_STREAMABLE_POINTER() is replaced by RW_DEFINE_EXTERNAL_STREAMABLE_POINTER(), RW_DEFINE_STREAMABLE_AS_SELF() is replaced by RW_DEFINE_EXTERNAL_STREAMABLE_AS_SELF(), and RW_BEGIN_STREAM_CONTENTS() is replaced by RW_BEGIN_EXTERNAL_STREAM_CONTENTS().

The macro calls used inside the streamContents() function may also be different. If the class in question inherits from another streamable class, then RW_STREAM_EXTERNAL_PARENT() is used instead of RW_STREAM_PARENT().

When used with external serialization, the RW_STREAM_ATTR_MEMBER() macro acts on public data members only. The RW_STREAM_ATTR_GET_SET() macro provides serialization of attributes for libraries with public "get" and "set" accessor functions. The following code shows a source module with external serialization macros. The sample represents the entire contents of the real_property_ext.cpp file.

//examples\serial\external\real_property_ext.cpp

//1

Note the use of the external version of this macro.

//2

The GET/SET macros provide access to data members via their get and set functions. The first parameter to the macro is the name of the data member, the second parameter is the type of the member, the third is the name of the get function (used by output streams to obtain a value), and the last parameter is the name of the set function (used by input streams to set a value).

//3

Again, note the use of the external version of the macros.


You can put calls for related classes together in the same header or source file in your code. Classes within a hierarchy must either use external serialization or use the intrusive style. Mixed hierarchies are not supported.

To support the derived class residential, you can add to the two real_property_ext files as follows.

//examples\serial\external\real_property_ext2.h

//1

Macro invocations for the residential class.

The next code sample is from: examples\serial\external\real_property_ext2.cpp.

//1

Note the use of the external version of this macro.

6.4.7 External Serialization of Templates and Nested Classes

The Serialization package does not have direct support for external serialization of template classes; however, template instances, nested classes, and any class type whose name is more than a simple identifier can be handled with typedefs.

In an earlier example, a nested class was used with a typedef of the form:

This typedef was then used in the macro invocations.

The same can be done with template classes, but serialization support must be provided for each template specialization separately, regardless of whether that specialization is explicit or implicit.

In the example in Section 6.4.4, "Templates," typedefs were used in the invocations of RW_DEFINE_EXTERNAL_STREAMABLE_POINTER. The typedefs looked like this:

These can also be used with RW_DECLARE_EXTERNAL_STREAM_FNS.



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.