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.
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.
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 its class declaration. 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 >*) )
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.
#include
the file <ospace/source.h> at the beginning of the source file.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 *)])
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 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 >& );
};
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.
#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
#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;
}
#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
#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
#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_;
}
#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
#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 ];
}
#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
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_Tm
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.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 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.
#ifndef BOX2_H
#define BOX2_H
template< class T >
class box
{
public:
void print() const;
T value_;
};
#endif
#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
#include "box.h"
#include <iostream>
template< class T >
void
box< T >::print() const
{
cout << "The box contains " << value_ << endl;
}
#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_;
}
#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.