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 .

  1. Add macros to a header file.
  2. Add a macro to a source file.
  3. Add the writer and reader functions for saving and restoring instances of the class.

These activities are discussed in more detail in the following sections.

Add Macros to a Header File

To add macros to a header file, perform the following steps.

  1. #include the file <ospace/header.h> at the beginning of the my_class header file.
  2. If my_class does not define or inherit any virtual functions, add the following macros after the closing brace of the class declaration.

OS_CLASS( my_class )

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

OS_POLY_FUNCTION ( (my_class*) )

OS_POLY_CLASS( my_class )

OS_STREAM_OPERATORS( my_class )

Add a Macro to a Source File

To add a macro to a source file, perform the following steps.

  1. #include the file <ospace/source.h> at the beginning of the source file for my_class .
  2. If 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.

OS_NO_FACTORY_STREAMABLE_
n ( (my_class*), id [,( b1 *), ... ,( bn *)] )

where

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 Writer and Reader Functions

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& );

  };
  

Examples

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.

<ospace/stream/examples/person.h>
#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
<ospace/stream/examples/person.cpp>
#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;
  }
  
Example <ospace/stream/examples/addown1.cpp>
#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.

<ospace/stream/examples/pet.h>
#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
<ospace/stream/examples/pet.cpp>
#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_;
  }
  
<ospace/stream/examples/cat.h>
#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
<ospace/stream/examples/cat.cpp>
#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;
  }
  
Example <ospace/stream/examples/addown2.cpp>
#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

A Non-Intrusive Approach

You can add streaming support to a class without modifying its header or source files, if both of the following are true.

To use this non-intrusive method of adding streaming support, modify the previous instructions for adding streaming support.

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.

<ospace/stream/examples/person2.h>
#ifndef PERSON2_H
#define PERSON2_H

#include <string>

class person
  {
  public:
    person() : buddy_( 0 ) {}
    void print() const;
    string my_name_;
    person* buddy_;
  };

#endif
<ospace/stream/examples/person2b.h>
#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

<ospace/stream/examples/person2.cpp>
#include "person.h"

void
person::print() const
  {
  cout << "name = " << my_name_ << " @ " << this << endl;
  cout << "buddy = " << buddy_->my_name_ << " @ " << buddy_;
  cout << endl << endl;
  }

<ospace/stream/examples/person2b.cpp>
#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_;
  }
  
Example <ospace/stream/examples/addown1b.cpp>
#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.