The C++ Stream Helpers
Ice for C++ uses a set of C++ templates and specializations of these templates to marshal and unmarshal C++ types. A type that can be marshaled and/or unmarshaled using this framework is known as a Streamable.
On this page:
Marshaling and Unmarshaling C++ Objects
Ice for C++ marshals C++ objects into streams of bytes, and unmarshals these C++ objects from streams of bytes. Ice for C++ has an internal implementation for these streams, and a publicly available implementation, and both implementations have mostly the same API.
To marshal a C++ object into a stream, you (or the generated code) can simply call write(obj)
on that stream. For basic types, such as int
or string
, there is a corresponding write function on the stream. For other types, write
is an inline template function that delegates to a specialization of the StreamHelper
template class:
namespace Ice { class OutputStream : public IceUtil::Shared { public: ... template<typename T> inline void write(const T& v) { StreamHelper<T, StreamableTraits<T>::helper>::write(this, v); } }; }
Likewise, when unmarshaling a C++ object from a stream, you (or the generated code) can simply call read(obj)
on that stream, and the reading of most types delegates to a specialization of this StreamHelper
template class:
namespace Ice { class InputStream : public IceUtil::Shared { public: ... template<typename T> inline void read(T& v) { StreamHelper<T, StreamableTraits<T>::helper>::read(this, v); } }; }
Ice looks for the StreamHelper
specializations in namespace Ice, so they must all be in namespace Ice.
Each StreamHelper
specialization typically provides a static write
function to write the C++ object into the steam, and a static read
function to read the C++ object from the stream. The stream object in these StreamHelper
static functions is represented by a template parameter type, which allows a StreamHelper
specialization to work with any stream implementation:
// Typical StreamHelper specialization namespace Ice { template<> struct StreamHelper<SomeType, StreamHelperCategoryXXX> { template<class S> static inline void write(S* stream, const SomeType& v) { ... marshal v... } template<class S> static inline void read(S* stream, SomeType& v) { ... unmarshal v ... } }; }
The base StreamHelper
template class is not defined, since there is no default way to read a C++ object from a stream or write this object into a stream:
namespace Ice { template<typename T, StreamHelperCategory st> struct StreamHelper; }
For most types, Ice for C++ provides or generates the corresponding StreamHelper
specialization (or partial specialization), for example all Slice sequences mapped to a C++ vector, C++ list or similar type use this specialization:
namespace Ice { template<typename T> struct StreamHelper<T, StreamHelperCategorySequence> { template<class S> static inline void write(S* stream, const T& v) { stream->writeSize(static_cast<Int>(v.size())); for(typename T::const_iterator p = v.begin(); p != v.end(); ++p) { stream->write(*p); } } template<class S> static inline void read(S* stream, T& v) { Int sz = stream->readAndCheckSeqSize(StreamableTraits<typename T::value_type>::minWireSize); T(sz).swap(v); for(typename T::iterator p = v.begin(); p != v.end(); ++p) { stream->read(*p); } } }; }
StreamableTraits
The StreamHelper
specializations use specializations of the StremableTraits
template class to retrieve some characteristics (or traits) of the C++ type it operates on:
namespace Ice { typedef int StreamHelperCategory; const StreamHelperCategory StreamHelperCategoryUnknown = 0; const StreamHelperCategory StreamHelperCategoryBuiltin = 1; const StreamHelperCategory StreamHelperCategoryStruct = 2; const StreamHelperCategory StreamHelperCategoryStructClass = 3; const StreamHelperCategory StreamHelperCategoryEnum = 4; const StreamHelperCategory StreamHelperCategorySequence = 5; const StreamHelperCategory StreamHelperCategoryDictionary = 6; const StreamHelperCategory StreamHelperCategoryProxy = 7; const StreamHelperCategory StreamHelperCategoryClass = 8; const StreamHelperCategory StreamHelperCategoryUserException = 9; template<typename T, typename Enabler = void> struct StreamableTraits { static const StreamHelperCategory helper = ...; static const int minWireSize = 1; static const bool fixedLength = false; }; }
In particular, StreamHelper
specializations are divided in various categories, to allow a single partial specialization of StreamHelper
to handle several similar C++ types. For example, Ice for C++ provides a single StreamHelper
partial specialization for all enum
types, with category StreamHelperCategoryEnum
:
template<typename T> struct StreamHelper<T, StreamHelperCategoryEnum> { template<class S> static inline void write(S* stream, const T& v) { ...sanity check... stream->writeEnum(static_cast<Int>(v), StreamableTraits<T>::maxValue); } template<class S> static inline void read(S* stream, T& v) { Int value = stream->readEnum(StreamableTraits<T>::maxValue); ..sanity check... v = static_cast<T>(value); } };
StreamHelperCategory
is an integer (int
). Values between 0 and 20 are reserved for Ice; you can use other values for your own StreamHelper
specializations.
A StreamableTraits
is always defined in namespace Ice and provides three static const
data members:
helper
The category ofStreamHelper
associated with this C++ type (see above). The baseStreamableTraits
template class computeshelper
automatically for maps, lists, vectors and similar types.minWireSize
The minimum size (in bytes) of the corresponding Slice type when marshaled using the Ice Encoding. The baseStreamableTraits
template class provides the default value, 1 byte. This value should be one for sequences and dictionaries, since an empty sequence or dictionary is marshaled as a single byte (size = 0).fixedLength
true
when the corresponding Slice type is marshaled on a fixed number of bytes, andfalse
when it is marshaled on a variable number of bytes. The default, provided by the baseStreamableTraits
template class, is false.
Ice provides StreamableTraits
specializations for all the C++ types it uses when mapping Slice types to C++. For example, Ice provides a StreamTraits<Ice::Long>
(for 64-bit signed integers) and a StreamableTraits< ::std::string>
:
namespace Ice { template<> struct StreamableTraits<Long> { static const StreamHelperCategory helper = StreamHelperCategoryBuiltin; static const int minWireSize = 8; static const bool fixedLength = true; }; template<> struct StreamableTraits< ::std::string> { static const StreamHelperCategory helper = StreamHelperCategoryBuiltin; static const int minWireSize = 1; static const bool fixedLength = false; }; }
For each constructed type, such as Slice struct
s, the Slice to C++ translator generates the corresponding StreamableTraits
specialization.
For example:
// Generated C++ code namespace Ice { template<> struct StreamableTraits< ::Ice::Identity> { static const StreamHelperCategory helper = StreamHelperCategoryStruct; static const int minWireSize = 2; static const bool fixedLength = false; };
Marshaling and Unmarshaling Optional Values
As described in Data Encoding for Optional Values, the encoding or wire representation of an optional data member or parameter consists of:
- An optional format (a 3-bits value) that depends on the associated Slice type. For example, the optional format for a
sequence<string>
isFSize
(or6)
. - The tag associated with this optional data member or parameter (this tag is a positive integer)
- The actual value of this optional data member or parameter
To marshal an optional value into a stream, you (or the generated code) call write(tag, obj)
on that stream, where tag
is the optional's tag (an int), and obj is the optional value. write
is a template function that uses a specialization of the StreamOptionalHelper
template class when this optional value is set:
namespace Ice { class OutputStream : public IceUtil::Shared { public: ... template<typename T> inline void write(Int tag, const IceUtil::Optional<T>& v) { if(v) { writeOptional(tag, StreamOptionalHelper<T, StreamableTraits<T>::helper, StreamableTraits<T>::fixedLength>::optionalFormat); StreamOptionalHelper<T, StreamableTraits<T>::helper, StreamableTraits<T>::fixedLength>::write(this, *v); } } }; }
Likewise, when unmarshaling an optional value from a stream, you (or the generated code) call read(tag, obj)
on that stream, which delegates to a specialization of the same StreamOptionalHelper
template class when the stream holds a value for this optional data member or parameter:
namespace Ice { class InputStream : public IceUtil::Shared { public: ... template<typename T> inline void read(Int tag, IceUtil::Optional<T>& v) { if(readOptional(tag, StreamOptionalHelper<T, StreamableTraits<T>::helper, StreamableTraits<T>::fixedLength>::optionalFormat)) { v.__setIsSet(); StreamOptionalHelper<T, StreamableTraits<T>::helper, StreamableTraits<T>::fixedLength>::read(this, *v); } else { v = IceUtil::None; } } }; }
Each StreamOptionalHelper
specialization usually provides static read
and write
functions, just like StreamHelper
specializations, and also an optionalFormat
static data member that provides the optional format of the corresponding Slice type.
For many types, the StreamOptionalHelper
specialization simply delegates to this type's StreamHelper
specialization, and computes optionalFormat
with the GetOptionalFormat
template:
// GetOptionalFormat is declared but never defined template<StreamHelperCategory st, int minWireSize, bool fixedLength> struct GetOptionalFormat; // Base StreamOptionalHelper template class namespace Ice { template<typename T, StreamHelperCategory st, bool fixedLength> struct StreamOptionalHelper { typedef StreamableTraits<T> Traits; static const OptionalFormat optionalFormat = GetOptionalFormat<st, Traits::minWireSize, fixedLength>::value; template<class S> static inline void write(S* stream, const T& v) { stream->write(v); } template<class S> static inline void read(S* stream, T& v) { stream->read(v); } }; }
For example, the optional format for an Ice::Long (64-bit signed integer, encoded on exactly 8 bytes) is provided by this specialization of GetOptionalFormat
:
namespace Ice { template<> struct GetOptionalFormat<StreamHelperCategoryBuiltin, 8, true> { static const OptionalFormat value = OptionalFormatF8; }; }