Adding Streaming Support to Non-Template Classes |
This section describes how to add binary streaming support to non-template classes.
To add streaming support, add several macros to your files. If you make a mistake when specifying the macros, compile-time, link-time, or runtime errors may be reported, depending on the type of mistake. For more information, refer to the "Common Mistakes" section of this chapter.
The simplest way to add streaming support is intrusive, which requires alteration of the class' header and source files. This intrusive approach is the first technique described. A similar but non-intrusive approach is presented later in this section.
To enable a non-template class for
binary streaming, perform the following activities, assuming the name of your
class is my_class .
These activities are discussed in more detail in the following sections.
To add macros to a header file, perform the following steps.
#include
the file <ospace/header.h> at the beginning of the my_class
header file.my_class
does not define or inherit any virtual functions, add the following macros
after the closing brace of the class declaration. OS_STREAM_OPERATORS(
my_class )
Otherwise, assuming my_class
defines or inherits at least one virtual function, add the following macros.
Insert the first macro inside the class declaration and then add the other two
macros after the closing brace of the class declaration. The first macro is
not necessary for compilers that support typeid and dynamic_cast . However,
failure to include the macro may produce source code that is not portable
To add a macro to a source file, perform the following steps.
#include
the file <ospace/source.h> at the
beginning of the source file for my_class .my_class
has a public default constructor and is not an abstract class, add the
following macro to the source file.
OS_STREAMABLE_ n (
(my_class*), id
[,( b1 *),
... ,( bn *)]
) Otherwise, assuming my_class
either does not have a public default constructor or is an abstract class, add
the following macro to the source file.
n
OS_NO_FACTORY_STREAMABLE_(
(my_class*), id [,(
b1 *), ...
,( bn *)] )
n
is the number of immediate base classes to my_class
.
id
is an optional unique ID for the class. Adding an ID is worthwhile if you have
objects that are binary encoded or if performance is an issue. If you set this
unique ID to os_use_name_as_id , the class name
is used for identification and the class ID is set to its default value of 0.
ID values less than os_user_start_id
are reserved for Recursion Software's <ToolKit> implementations. You can
use any ID value greater than os_user_start_id ,
but less than INT_MAX .
b1
is the first base class of my_class
.
bn
is the n th base class of my_class
.
A concrete class without a
public default constructor cannot be streamed into a pointer. A concrete class
is any class without pure virtual functions and containing at least one public
constructor. Without a public default constructor, os_bstream
cannot create heap instances of the class. Therefore, you must create the
object using another constructor and stream into the existing object.
Add the writer and reader
functions. The writer function is always a non-member function called os_write()
, which specifies how the data defined in an
instance of my_class is written to a stream. The
writer function must be written according to the following pattern.
void
os_write( os_bstream& stream, const my_class& object )
{
// 1. If my_class has base classes, call os_write() for each base
// class to save the base sub-objects. To do this, call os_write()
// for each base class with `object' cast to a reference to the
// base class.
// 2. Stream the data members of object to stream in the order
// that they were declared.
// (i.e. stream << object.member1 << object.member2 ...)
}
The reader function is always a
non-member function called os_read() , which
specifies how the data defined in an instance of my_class
is read from a stream. The reader function must be written according to the
following pattern.
void
os_read( os_bstream& stream, my_class& object )
{
// 1. If necessary, deallocate any heap-based data members.
// 2. If my_class has base classes, call os_read() for each
// base class to restore the base sub-objects. To do this,
// call os_read() for each base class with `object' cast to
// a reference to the base class.
// 3. Read the data members of object from stream in the order
// that they were declared.
// (i.e. stream >> object.member1 >> object.member2 ...)
}
These functions should be
forward-declared in the header file before the OS_STREAM_OPERATORS
macro. The function bodies should be defined in a
source file and typically are placed in the source file of my_class
.
If os_write()
or os_read() need to access data that is private
to my_class , declare each function as a friend
function of my_class , as in the following
example.
class my_class
{
friend void os_write( os_bstream&, const my_class& );
friend void os_read( os_bstream&, my_class& );
};
The following example,
addown1.cpp, is comprised of three files. This example adds binary streaming
support to a simple class called person , which
defines two public data members. Note that the OS_CLASS
macro is used because person does not inherit or
define any virtual functions. The address of each object is printed to show
that mutual pointer relationships are correctly restored.
#ifndef PERSON_H
#define PERSON_H
#include <string>
#include <ospace/header.h>
class person
{
public:
person() : buddy_( 0 ) {}
void print() const;
string my_name_;
person* buddy_;
};
// Forward declare writer and reader functions.
void os_write( os_bstream&, const person& );
void os_read( os_bstream&, person& );
OS_CLASS( person )
OS_STREAM_OPERATORS( person )
#endif
#include "person.h"
#include <ospace/source.h>
#include <ospace/uss/std/string>
OS_STREAMABLE_0( (person*), os_use_name_as_id ) // 0 base classes.
void
os_write( os_bstream& stream, const person& object )
{
stream << object.my_name_ << object.buddy_;
}
void
os_read( os_bstream& stream, person& object )
{
stream >> object.my_name_ >> object.buddy_;
}
void
person::print() const
{
cout << "name = " << my_name_ << " @ " << this << endl;
cout << "buddy = " << buddy_->my_name_ << " @ " << buddy_;
cout << endl << endl;
}
#include "person.h"
#include <fstream>
#include <ospace/stream.h>
void
write()
{
fstream file( "addown1.out", ios::binary | ios::out | ios::trunc );
os_bstream stream( os_adapter_for( file ) );
person* p1 = new person;
p1->my_name_ = "Graham";
person* p2 = new person;
p2->my_name_ = "David";
p1->buddy_ = p2; // David is Graham's buddy.
p2->buddy_ = p1; // Graham is David's buddy.
stream << p1; // Stream p1 and its associated data members.
delete p1;
delete p2;
}
void
read()
{
fstream file( "addown1.out", ios::binary | ios::in );
os_bstream stream( os_adapter_for( file ) );
person* p1;
stream >> p1; // Restore person and its associated data members.
person* p2 = p1->buddy_; // Who is p1's buddy?
cout << "p1 = ";
p1->print();
cout << "p2 = ";
p2->print();
delete p1;
delete p2;
}
void
main()
{
os_streaming_toolkit initialize;
write();
read();
}
p1 = Graham @ 0x85de8
buddy = David @ 0x86e48
p2 = David @ 0x86e48
buddy = Graham @ 0x85de8
The next example,
addown2.cpp, is comprised of five files. This example adds binary streaming
support to two classes called pet and cat
. Note that both header files utilize the OS_POLY_FUNCTION
and OS_POLY_CLASS macros, because both
classes either inherit or define virtual functions. OS_POLY_FUNCTION is not
necessary for compilers that support typeid and dynamic_cast . However,
failure to include the macro may produce source code that is not portable.
#ifndef PET_H
#define PET_H
#include <string>
#include <ospace/header.h>
class pet
{
public:
virtual void print() const = 0;
string my_name_;
OS_POLY_FUNCTION( (pet*) )
};
// Forward declare writer and reader functions.
void os_write( os_bstream& stream, const pet& );
void os_read( os_bstream& stream, pet& );
OS_POLY_CLASS( pet )
OS_STREAM_OPERATORS( pet )
#endif
#include "pet.h"
#include <ospace/source.h>
#include <ospace/uss/std/string.h>
OS_NO_FACTORY_STREAMABLE_0( (pet*), os_use_name_as_id ) // 0 base classes
void
os_write( os_bstream& stream, const pet& object )
{
stream << object.my_name_;
}
void
os_read( os_bstream& stream, pet& object )
{
stream >> object.my_name_;
}
#ifndef CAT_H
#define CAT_H
#include "pet.h"
#include <ospace/header.h>
class cat : public pet
{
public:
cat() : longhair_( false ) {}
/* virtual */ void print() const;
bool longhair_;
OS_POLY_FUNCTION( (cat*) )
};
// Forward declare writer and reader functions.
void os_write( os_bstream& stream, const cat& );
void os_read( os_bstream& stream, cat& );
OS_POLY_CLASS( cat )
OS_STREAM_OPERATORS( cat )
#endif
#include "cat.h"
#include <iostream>
#include <ospace/stream.h>
#include <ospace/source.h>
OS_STREAMABLE_1( (cat*), os_use_name_as_id, (pet*) ) // 1 base class
void
os_write( os_bstream& stream, const cat& object )
{
os_write( stream, (const pet&) object );
stream << object.longhair_;
}
void
os_read( os_bstream& stream, cat& object )
{
os_read( stream, (pet&) object );
stream >> object.longhair_;
}
/* virtual */ void
cat::print() const
{
if ( longhair_ )
cout << "long hair";
else
cout << "short hair";
cout << " cat called " << my_name_ << endl;
}
#include "cat.h"
#include <fstream>
#include <ospace/stream.h>
void
write()
{
fstream file( "addown2.out", ios::binary | ios::out | ios::trunc );
os_bstream stream( os_adapter_for( file ) );
cat* my_pet = new cat; // Create a cat on the heap.
my_pet->my_name_ = "Agatha";
my_pet->longhair_ = true;
cout << "Write ";
my_pet->print(); // Print the cat.
stream << my_pet;
delete my_pet;
}
void
read()
{
fstream file( "addown2.out", ios::binary | ios::in );
os_bstream stream( os_adapter_for( file ) );
pet* my_pet = 0;
stream >> my_pet; // Stream the pet onto the heap.
cout << "Read ";
my_pet->print(); // Print the pet.
delete my_pet;
}
void
main()
{
os_streaming_toolkit initialize;
write();
read();
}
Write long hair cat called Agatha
Read long hair cat called Agatha
You can add streaming support to a class without modifying its header or source files, if both of the following are true.
typeid and dynamic_cast
.To use this non-intrusive method of adding streaming support, modify the previous instructions for adding streaming support.
OS_CLASS
and OS_STREAM_OPERATORS macros directly into
the header file of my_class , place them into
a new and separate header file. If the class defines or inherits virtual
functions and all compilers under consideration support typeid
and dynamic_cast , place the OS_POLY_CLASS
and OS_STREAM_OPERATORS into a new and
separate header file.OS_STREAMABLE_n
macro and the writer/reader functions directly into the source file of my_class
, place them into a new and separate source file that is linked into the
final executable. The new header and source file
names should indicate that these files are the binary enabled versions of my_class
. Example file names are my_classb.h and my_classb.cpp.
In the following example, addown1b.cpp is comprised of five files. This example, though similar to the addown1.cpp example, uses the non-intrusive approach. Note that the person2.h and person2.cpp files do not contain Streaming<ToolKit> code.
#ifndef PERSON2_H
#define PERSON2_H
#include <string>
class person
{
public:
person() : buddy_( 0 ) {}
void print() const;
string my_name_;
person* buddy_;
};
#endif
#ifndef PERSON2B_H
#define PERSON2B_H
#include "person2.h"
#include <ospace/header.h>
// Declare writer and reader functions.
void os_write( os_bstream&, const person& );
void os_read( os_bstream&, person& );
OS_CLASS( person )
OS_STREAM_OPERATORS( person )
#endif
#include "person.h"
void
person::print() const
{
cout << "name = " << my_name_ << " @ " << this << endl;
cout << "buddy = " << buddy_->my_name_ << " @ " << buddy_;
cout << endl << endl;
}
#include "person2b.h"
#include <ospace/source.h>
OS_STREAMABLE_0( (person*), os_use_name_as_id )
void
os_write( os_bstream& stream, const person& object )
{
stream << object.my_name_ << object.buddy_;
}
void
os_read( os_bstream& stream, person& object )
{
stream >> object.my_name_ >> object.buddy_;
}
#include "person2b.h"
#include <fstream>
#include <ospace/stream.h>
void
write()
{
fstream file( "addown1.out", ios::binary | ios::out | ios::trunc );
os_bstream stream( os_adapter_for( file ) );
person* p1 = new person;
p1->my_name_ = "Graham";
person* p2 = new person;
p2->my_name_ = "David";
p1->buddy_ = p2; // David is Graham's buddy.
p2->buddy_ = p1; // Graham is David's buddy.
stream << p1; // Stream p1 and its associated data members.
delete p1;
delete p2;
}
void
read()
{
fstream file( "addown1.out", ios::binary | ios::in );
os_bstream stream( os_adapter_for( file ) );
person* p1;
stream >> p1; // Restore person and its associated data members.
person* p2 = p1->buddy_; // Who is p1's buddy?
cout << "p1 = " << p1->my_name_ << " @ " << p1 << endl;
cout << "buddy = " << p1->buddy_->my_name_ << " @ " << p1->buddy_;
cout << endl << endl;
cout << "p2 = " << p2->my_name_ << " @ " << p2 << endl;
cout << "buddy = " << p2->buddy_->my_name_ << " @ " << p2->buddy_;
cout << endl;
delete p1;
delete p2;
}
void
main()
{
os_streaming_toolkit initialize;
write();
read();
}
p1 = Graham @ 0x85de8
buddy = David @ 0x86e48
p2 = David @ 0x86e48
buddy = Graham @ 0x85de8
Copyright©1994-2026 Recursion
Software LLC
All Rights Reserved - For use by licensed users only.