6.5 Advanced Examples
This section provides examples of more advanced operations, such as gaining more fine-grained control over serialization and integrating streaming with legacy Rogue Wave products. The examples in this section are taken from the examples\serial\advanced directory.
6.5.1 Using Object Streams with RWCollectable-Derived Objects
Because the format used by RWCompactObjectInputStreamImp for storing data is the same as that used by objects derived from RWCollectable, it is possible to interleave the two. The header file \rw\serial\RWCollectableIO.h contains definitions of insertion and extraction operators for RWCollectable. Aside from including this header, the only other requirement is that the macro RW_DEFINE_COLLECTABLE_AS_STREAMABLE be defined for each RWCollectable-based object. This will also allow you to stream out these RWCollectable-based objects to other kinds of object streams, but the effect will be to embed compact object stream formatted data into the stream. The following example demonstrates how to get the compact format working without adding all the additional code to your existing RWCollectable-based classes. (If you want to convert RWCollectable-based objects to stream properly in any object stream format, go to Section 6.5.2).
 
// examples\serial\advanced\tree.h)
 
#include <rw/serial/RWObjectStreamMacros.h>
#include <rw/serial/RWCollectableIO.h> // 1
 
#define EXPORT // 2
 
class tree : public RWCollectable // 3
{
RW_DECLARE_COLLECTABLE_CLASS(EXPORT, tree); // 4
 
public:
tree() { }
tree(const RWCString& species)
: species_(species) { }
virtual void restoreGuts(RWvistream&);
virtual void saveGuts(RWvostream&) const;
virtual void restoreGuts(RWFile&) { }
virtual void saveGuts(RWFile&) const { }
RWCString species_;
};
RW_DEFINE_COLLECTABLE_AS_STREAMABLE(tree) // 5
 
//1 Include these files to get serialization macros and the inserters and extractors for RWCollectable.
//2 Macro for the exporting of symbols. For more information on how to define and use the EXPORT macro, see Section 6.16.1.2, “Exporting Symbols in a Windows Environment,” in the Essential Tools Module User’s Guide.
//3 New class derived from RWCollectable.
//4 Existing macro from Rogue Wave persistence.
//5 Make your collectable base class streamable.
 
// examples\serial\advanced\forest.cpp
 
tree fir("Noble Fir"); // 1
 
RWpostream postr(cout);
RWObjectOutputStream out = RWCompactObjectOutputStreamImp:: // 2
make(RWDataToVirtualOutputStreamImp::make(postr));
 
out << &fir; // 3
 
//1 Create a tree.
//2 Create a data stream using the standard file stream just opened and then create a compact object output stream from the data stream.
//3 Stream out the tree.
If you want to convert RWCollectable-based objects to stream properly in any object stream format, see Section 6.5.2.
6.5.2 Making RWCollectable Objects That Are Also Serializable
To serialize RWCollectable-based objects to any object stream, it is necessary to do the full object stream conversion. Everything that you would do for any other class must now be done for the RWCollectable-based class. You can’t alter the RWCollectable class, so you must use the external technique for providing the streaming capability. For a full description, see Section 6.4.6. Also, you must not include RWCollectableIO.h or use RW_DEFINE_COLLECTABLE_AS_STREAMABLE, since you are using the other macros to accomplish this. Instead, you must use RW_DECLARE_STREAMABLE_COLLECTABLE in a header file and RW_DEFINE_STREAMABLE_COLLECTABLE in a source file. This edition has taken this burden from the developer and has provided these in the library. Including the rw/serial/serial.h header gets the RW_DECLARE_STREAMABLE_COLLECTABLE which is all that is needed to get the serialization support for RWCollectable.
A simple example follows.
 
// examples\serial\advanced\tree2.h
 
#include <rw/serial/serial.h>
 
#define EXPORT // 1
 
class tree : public RWCollectable // 2
{
RW_DECLARE_COLLECTABLE_CLASS(EXPORT, tree); // 3
 
public:
tree() { }
tree(const RWCString& species)
: species_(species) { }
virtual void restoreGuts(RWvistream&);
virtual void saveGuts(RWvostream&) const;
virtual void restoreGuts(RWFile&) { }
virtual void saveGuts(RWFile&) const { }
RWCString getSpecies() const {
return species_;
}
RWCString setSpecies(const RWCString& species) {
RWCString tmp = species_;
species_ = species;
return tmp;
}
private:
RWCString species_;
};
 
RW_DECLARE_EXTERNAL_STREAMABLE_AS_SELF(tree)
RW_DECLARE_EXTERNAL_STREAMABLE_AS_BASE(tree, RWCollectable)
RW_DECLARE_STREAMABLE_POINTER(tree) // 4
RW_DECLARE_EXTERNAL_STREAM_FNS(tree) // 5
//1 Macro for the exporting of symbols. For more information on how to define and use the EXPORT macro, see Section 6.16.1.2, “Exporting Symbols in a Windows Environment,” in the Essential Tools Module User’s Guide.
//2 New class derived from RWCollectable
//3 Existing macro from Rogue Wave persistence.
//4 Declare the new class as streamable by reference.
//5 Declare an external streaming function for the new class.
 
// examples\serial\advanced\tree2.cpp
 
RW_BEGIN_EXTERNAL_STREAM_CONTENTS(tree) // 1
{
RW_STREAM_ATTR_GET_SET(species, RWCString, getSpecies, setSpecies)
}
RW_END_STREAM_CONTENTS
 
RW_DEFINE_EXTERNAL_STREAMABLE_AS_SELF(tree)
RW_DEFINE_EXTERNAL_STREAMABLE_AS_BASE(tree, RWCollectable)
RW_DEFINE_EXTERNAL_STREAMABLE_POINTER(tree)
//1 Define the streamContents() function for the new class.
//2 Define our new class as streamable by reference.
 
// examples\serial\advanced\forest2.cpp
 
tree* fir = new tree("Noble Fir"); // 1
RWpostream postr(cout);
RWObjectOutputStream out = RWCompactObjectOutputStreamImp::
make(RWDataToVirtualOutputStreamImp::
make(postr)); // 2
 
out << fir; // 3
//1 Create a tree.
//2 Create a data stream using the standard file stream just opened and then create a compact object output stream from the data stream.
//3 Stream out the tree.
6.5.3 Writing Custom streamContents() Functions
For classes with complicated designs, the Serialization package includes macros that enable you to use custom code.
For example, your program might use a class that has a C++ array as a data member. You can pass C++ statements to the RW_WHEN_INPUT() macro in its body argument; the statements are executed only during an input operation. You can also pass C++ statements to the RW_WHEN_OUTPUT() macro in its body argument; these statements are executed only during an output operation.
Within custom code, the current object input stream can be referred to using the RW_INPUT_STREAM macro, and the current object output stream by the RW_OUTPUT_STREAM macro.
 
// examples\serial\other\residential.h
 
class residential : public real_property
{
RW_DECLARE_VIRTUAL_STREAM_FNS(residential)
RW_DECLARE_FRIEND_CTOR_METHOD(real_property, residential)
 
public:
residential()
: shown_(0), dates_shown_(0)
{}
 
residential(const RWCString& address, const RWCString& size,
unsigned long footage, unsigned long shown,
const RWDate* dates)
: real_property(address, size), footage_(footage),
shown_(shown), dates_shown_(0) {
shown_ = shown;
if (shown) {
dates_shown_ = new RWDate[shown];
for (size_t i = 0; i < shown; i++) {
dates_shown_[i] = dates[i];
}
}
}
 
~residential() {
delete [] dates_shown_;
}
 
bool operator== (const residential& prop) const {
return real_property::operator==(prop);
}
 
private:
long footage_;
long shown_;
RWDate *dates_shown_; // 1
};
//1 The dates_shown_ member is an array of dates. The shown_ member indicates how many elements are in the array. In this example the array passed as a parameter to the constructor is copied element by element into the newly allocated array pointed to by dates_shown_. dates_shown_ is always 0 if the array is empty.
The custom streamContents() function now looks like the following code sample:
 
// examples\serial\other\residential.cpp
 
RW_BEGIN_STREAM_CONTENTS(residential)
{
RW_STREAM_PARENT(real_property)
RW_STREAM_ATTR_MEMBER(footage, footage_)
RW_STREAM_ATTR_MEMBER(shown, shown_) //1
RW_WHEN_INPUT( {
if (shown_) {
delete dates_shown_;
dates_shown_ = new RWDate[shown_]; //2
}
 
for (unsigned long i = 0; i < shown_; i++) {
RW_INPUT_STREAM >> dates_shown_[i];
}
})
 
RW_WHEN_OUTPUT( {
for (unsigned long i = 0; i < shown_; i++) { //3
RW_OUTPUT_STREAM << dates_shown_[i];
}
})
}
RW_END_STREAM_CONTENTS
//1 Read in or write out the size of the array.
//2 Reallocate the array when reading data in. This macro produces no code for an output stream. The size is already obtained from the stream.
//3 Iterate through the array, reading or writing as appropriate for the stream. The code passed to the RW_WHEN_INPUT macro is used for input streams, and the code passed to the RW_WHEN_OUTPUT macro is used for output streams.
6.5.4 Controlling the Scope of Object Reference Resolution
Every time an object is inserted into an object stream, a context is established for the resolution of object references. For instance, the following code establishes a scope that matches the scope of the object cat. Object references contained within cat will be matched up as required.
 
// examples\serial\advanced\catanddog.h
 
class cat
{
RW_DECLARE_VIRTUAL_STREAM_FNS(cat)
public:
cat() { }
 
virtual ~cat() { }
 
cat(const RWCString color) : color_(color) { }
private:
RWCString color_;
};
 
cat* yellow_cat = new cat("yellow");
 
out << yellow_cat
6.5.4.1 Streaming Out Multiple Objects
But what happens when you stream out two objects, or three? What if we stream out cat followed by dog and then cat again?
 
dog* blue_dog = new dog("blue");
 
out << yellow_cat << blue_dog << yellow_cat;
 
Now you have three separate scopes reference resolution. If the same reference occurs in both contexts, as yellow_cat does here, it will be streamed out twice, once for each scope.
6.5.4.2 Streaming Multiple Objects into One Document
But what if you really only want one context? What if you want the second instance of yellow_cat in the stream to simply refer to the first one, thus preserving the fact that they are really the same object. One way to do this is to make use of the same mechanism employed by the streams to create these contexts in the first place. You do this by using the guard classes RWWithObjectOutputContext (for output) and RWWithObjectInputContext (for input). By creating these guards, you effectively create your own master context for resolution of object references.
To get both instances of streaming yellow_cat in the same context, all you have to do is this:
 
// examples\serial\advanced\catanddog.cpp
 
{ // 1
RWWithObjectOutputContext context(out); // 2
out << yellow_cat << blue_dog << yellow_cat; // 3
}
//1 Create a C++ context for our guard to work in.
//2 Create a streaming context, using the guard class. What this guard class does is to call the stream function openContext() on construction and closeContext() on destruction.
//3 Stream out as before.
You can achieve the same result by calling those functions directly:
 
out.openContext();
out << yellow_cat << blue_dog << yellow_cat;
out.closeContext();
But using the guard is a safer.
So now you have a single context containing all pets. The second time you stream out yellow_cat, the stream will contain a reference to the first yellow_cat rather than a whole new copy.
The code for input needs to match the code for output:
 
{
dog* blue_dog2;
cat* yellow_cat2;
cat* yellow_cat3;
RWWithObjectInputContext(in);
in << yellow_cat2 << blue_dog2 << yellow_cat3;
}
 
Now you have your pet collection. Both pointers yellow_cat2 and yellow_cat3 point to the same cat, as they should.