Adding Streaming Support to Template Classes


This section describes how to add binary streaming support to 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 in this chapter.

Some compilers have problems with certain template combinations. Consult the release notes for information about known compiler problems and workarounds for them.

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 template class for binary streaming, perform the following activities.

  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.

The following instructions assume the name of this class is my_class< P1,,Pm > and use the symbol m to denote the number of template class parameters. These instructions, though similar to those used for non-template classes, use different macros.

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 its class declaration.

OS_CLASS_T m ( my_class )

OS_STREAM_OPERATORS_T m ( my_class )

Otherwise, assuming that my_class defines or inherits at least one virtual function, add the following macros. Insert the first macro inside its class declaration and add the other 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< P1,,Pm >*) )

OS_POLY_CLASS_T m ( my_class )

OS_STREAM_OPERATORS_T m ( my_class )

Add a Macro to a Source File

Add a macro to a source file for each instantiation of the template class. The relationship of a particular macro to a particular source file is not important, as long as you link the resulting object code into the final application.

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.
  2. If my_class has a default constructor and is not an abstract class, add the following macro to the source file.

OS_STREAMABLE_ n ( (my_class< Q1 ,, Qm >*), id [ ,( b1 *), ,( bn *) ] )

Otherwise, assuming my_class either does not have a default constructor or is an abstract class, add the following macro to the source file.

OS_NO_FACTORY_STREAMABLE_ n ((my_class< Q1 , , Qm >*),
id [,(b1*), ,(b n *)])

where

n is the number of immediate base classes to my_class .

Q1 , ... , Qm are the actual template arguments of the instantiation of my_class<P1,,Pm> .

id is a unique integer code for the particular instantiation my_class< Q1 ,, Qm > . Each instantiation of a template class needs a unique ID, because each template instantiation represents a separate C++ class. Do not use os_use_name_as_id for this argument, due to the variance in compiler support for default template parameters.

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 template functions. The writer function is always a non-member template 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.

template< class P1,, class Pm >
void
os_write( os_bstream& stream, const my_class< P1,,Pm >& 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 template 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.

template< class P1,, class Pm >
void
os_read( os_bstream& stream, my_class< P1,,Pm >& 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_Tm 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() needs to access data that is private to my_class , declare each function as a friend function of my_class , as in the following example.

template< class P1,, class Pm >
class my_class
  {
  friend void os_write( os_bstream&, const my_class< P1,,Pm >& );
  friend void os_read( os_bstream&, my_class< P1,,Pm >& );

  };

Examples

The following example, addown3.cpp, is comprised of three files and adds binary streaming support to a template class called box . Note that the OS_CLASS_T1 macro is used because box does not inherit or define any virtual functions.

<ospace/stream/examples/box.h>
#ifndef BOX_H
#define BOX_H

#include <ospace/header.h>

template< class T >
class box
  {
  public:
    void print() const;
    T value_;
  };

// Forward declare writer and reader functions.
template< class T> void os_write( os_bstream&, const box< T >& );
template< class T > void os_read( os_bstream&, box< T >& );

OS_CLASS_T1( box )
OS_STREAM_OPERATORS_T1( box )
#endif

<ospace/stream/examples/box.cpp>
#include "box.h"
#include <iostream>
#include <ospace/source.h>

template< class T >
void
os_write( os_bstream& stream, const box< T >& object )
  {
  stream << object.value_;
  }

template< class T >
void
os_read( os_bstream& stream, box< T >& object )
  {
  stream >> object.value_;
  }

template< class T >
void
box< T >::print() const
  {
  cout << "The box contains " << value_ << endl;
  }
  
Example <ospace/stream/examples/addown3.cpp>
#include "box.h"
#include <fstream>
#include <string>
#include <ospace/stream.h>
#include <ospace/source.h>

// Add source macro for each separate instantation of box.
OS_STREAMABLE_0( (box< int >*), 2000 )
OS_STREAMABLE_0( (box< string >*), 2001 )

void
write()
  {
  fstream file( "addown3.out", ios::out | ios::trunc );
  os_bstream stream( os_adapter_for( file ) );

  box< int > b1;
  b1.value_= 42;
  b1.print();
  stream << b1;

  box< string > b2;
  b2.value_ = "Life";
  b2.print();
  stream << b2;
  }

void
read()
  {
  fstream file( "addown3.out", ios::in  | ios::nocreate );
  os_bstream stream( os_adapter_for( file ) );

  box< int > b1;
  stream >> b1;
  b1.print();

  box< string > b2;
  stream >> b2;
  b2.print();
  }

void
main()
  {
  os_streaming_toolkit initialize;

  write();
  read();
  }

The box contains 42
The box contains Life
The box contains 42
The box contains Life

The next example, addown4.cpp, is comprised of five files. This example adds binary streaming support both to a non-template class called collection and to a template class called array . Note that both header files utilize the OS_POLY_FUNCTION and OS_POLY_CLASS macros, because both classes either inherit or define virtual functions. Note also that the source macros for array are placed into the main program and that the array class has been kept intentionally minimal for simplicity. The POLY_FUNCTION 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

<ospace/stream/examples/collect.h>
#ifndef COLLECT_H
#define COLLECT_H

#include <ospace/header.h>

class collection
  {
  public:
    collection( int n = 0 ) : size_( n ) {}
    virtual ~collection() {}
    virtual int capacity() const = 0;
    int size() const { return size_; }
    int size_;
    OS_POLY_FUNCTION( (collection*) )
  };

// Forward declare writer and reader functions.
void os_write( os_bstream& stream, const collection& );
void os_read( os_bstream& stream, collection& );

OS_POLY_CLASS( collection )
OS_STREAM_OPERATORS( collection )
#endif

<ospace/stream/examples/collect.cpp>
#include "collect.h"
#include <ospace/source.h>

OS_NO_FACTORY_STREAMABLE_0( (collection*), os_use_name_as_id )

void
os_write( os_bstream& stream, const collection& object )
  {
  stream << object.size_;
  }

void
os_read( os_bstream& stream, collection& object )
  {
  stream >> object.size_;
  }

<ospace/stream/examples/array.h>
#ifndef ARRAY_H
#define ARRAY_H

#include "collect.h"

template< class T >
class array : public collection
  {
  public:
    array( int n = 0 ) : collection( n ) { data_ = new T[ size() ]; }
    array( const array& a );
    ~array() { delete [] data_; }
    array& operator=( const array& a );
    T& operator[]( int i ) { return data_[ i ]; }
    const T& operator[]( int i ) const { return data_[ i ]; }
    /* virtual */ int capacity() const { return size(); }
    T* data_;
    OS_POLY_FUNCTION( (array< T >*) )
  };

// Forward declare writer and reader functions.
template< class T> void os_write( os_bstream&, const array< T >& );
template< class T > void os_read( os_bstream&, array< T >& );

OS_POLY_CLASS_T1( array )
OS_STREAM_OPERATORS_T1( array )
#endif
<ospace/stream/examples/array.cpp>
#include "array.h"
#include <ospace/source.h>

template< class T >
void
os_write( os_bstream& stream, const array< T >& object )
  {
  os_write( stream, (const collection&) object );
  for( int i = 0; i < object.size(); i++ )
    stream << object[ i ];
  }

template< class T >
void
os_read( os_bstream& stream, array< T >& object )
  {
  os_read( stream, (collection&) object );
  delete [] object.data_;
  object.data_ = new T[ object.size() ];
  for( int i = 0; i < object.size(); i++ )
    stream >> object[ i ];
  }

template< class T >
array< T >::array( const array< T >& a ) :
  collection( a.size_ )
  {
  data_ = new T[ size() ];
  for ( int i = 0; i < size(); i++ )
    *this[ i ] = a[ i ];
  }

template< class T>
array< T >&
array< T >::operator=( const array< T >& a )
  {
  delete [] data_;
  size_ = a.size_;
  data_ = new T[ size() ];
  for ( int i = 0; i < size(); i++ )
    *this[ i ] = a[ i ];
  }
  
Example <ospace/stream/examples/addown4.cpp>
#include "array.h"
#include <fstream>
#include <string>
#include <ospace/stream.h>
#include <ospace/source.h>

// Add source macro for each separate instantation of array.
OS_STREAMABLE_1( (array< int >*), 2000, (collection*) )
OS_STREAMABLE_1( (array< string >*), 2001, (collection*) )

void
write()
  {
  fstream file( "addown4.out", ios::binary | ios::out | ios::trunc );
  os_bstream stream( os_adapter_for( file ) );

  array< int > int_array( 10 );
  for ( int i = 0; i < int_array.size(); i++ )
    int_array[ i ] = i;
  stream << int_array;

  array< string > string_array( 2 );
  string_array[ 0 ] = "anil";
  string_array[ 1 ] = "bowls";
  stream << string_array;
  }

void
read()
  {
  fstream file( "addown4.out", ios::binary | ios::in );
  os_bstream stream( os_adapter_for( file ) );

  array< int > int_array;
  stream >> int_array;
  for ( int i = 0; i < int_array.size(); i++ )
    cout << int_array[ i ] << " ";
  cout << endl;

  array< string > string_array;
  stream >> string_array;
  for ( i = 0; i < string_array.size(); i++ )
    cout << string_array[ i ] << " ";
  cout << endl;
  }

void
main()
  {
  os_streaming_toolkit initialize;

  write();
  read();
  }

0 1 2 3 4 5 6 7 8 9
anil bowls

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 version of my_class . Example file names are my_classb.h and my_classb.cpp .

In the following example, addown3b.cpp is comprised of five files. This example, though similar to the addown3.cpp example, uses the non-intrusive approach. Note that the box2.h and box2.cpp files do not contain Streaming<ToolKit> code.

<ospace/stream/examples/box2.h>
#ifndef BOX2_H
#define BOX2_H

template< class T >
class box
  {
  public:
    void print() const;
    T value_;
  };

#endif
<ospace/stream/examples/box2b.h>
#ifndef BOX2B_H
#define BOX2B_H

#include "box2.h"
#include <ospace/header.h>

// Forward declare writer and reader functions.
template< class T> void os_write( os_bstream&, const box< T >& );
template< class T > void os_read( os_bstream&, box< T >& );

OS_CLASS_T1( box )
OS_STREAM_OPERATORS_T1( box )
#endif
<ospace/stream/examples/box2.cpp>
#include "box.h"
#include <iostream>

template< class T >
void
box< T >::print() const
  {
  cout << "The box contains " << value_ << endl;
  }

<ospace/stream/examples/box2b.cpp>
#include "box2b.h"
#include <ospace/source.h>

template< class T >
void
os_write( os_bstream& stream, const box< T >& object )
  {
  stream << object.value_;
  }

template< class T >
void
os_read( os_bstream& stream, box< T >& object )
  {
  stream >> object.value_;
  }
  
Example <ospace/stream/examples/addown3b.cpp>
#include "box2b.h"
#include <fstream>
#include <string>
#include <ospace/stream.h>
#include <ospace/source.h>

// Add source macro for each separate instantation of box.
OS_STREAMABLE_0( (box< int >*), 2000 )
OS_STREAMABLE_0( (box< string >*), 2001 )

void
write()
  {
  fstream file( "addown3.out", ios::binary | ios::out | ios::trunc );
  os_bstream stream( os_adapter_for( file ) );

  box< int > b1;
  b1.value_= 42;
  b1.print();
  stream << b1;

  box< string > b2;
  b2.value_ = "Life";
  b2.print();
  stream << b2;
  }

void
read()
  {
  fstream file( "addown3.out", ios::binary | ios::in );
  os_bstream stream( os_adapter_for( file ) );

  box< int > b1;
  stream >> b1;
  b1.print();

  box< string > b2;
  stream >> b2;
  b2.print();
  }

void
main()
  {
  os_streaming_toolkit initialize;

  write();
  read();
  }

The box contains 42
The box contains Life
The box contains 42
The box contains Life

Copyright©1994-2026 Recursion Software LLC
All Rights Reserved - For use by licensed users only.