BDE 4.14.0 Production release
Loading...
Searching...
No Matches
Package bslx

Modules

 bslx_byteinstream
 Provide a stream class for unexternalization of fundamental types.
 
 bslx_byteoutstream
 Provide a stream class for externalization of fundamental types.
 
 bslx_genericinstream
 Unexternalization of fundamental types from a parameterized stream.
 
 bslx_genericoutstream
 Externalization of fundamental types to a parameterized stream.
 
 bslx_instreamfunctions
 Facilitate uniform unexternalization of user and fundamental types.
 
 bslx_marshallingutil
 Support platform-independent marshalling of fundamental types.
 
 bslx_outstreamfunctions
 Facilitate uniform externalization of user and fundamental types.
 
 bslx_streambufinstream
 Unexternalization of fundamental types from a bsl::streambuf.
 
 bslx_streambufoutstream
 Externalization of fundamental types to a bsl::streambuf.
 
 bslx_testinstream
 Enable unexternalization of fundamental types with identification.
 
 bslx_testinstreamexception
 Provide an exception class for unexternalization operations.
 
 bslx_testoutstream
 Enable externalization of fundamental types with identification.
 
 bslx_typecode
 Enumerate the fundamental types supported by BDEX.
 
 bslx_versionfunctions
 Provide functions to return BDEX version information for types.
 

Detailed Description

Purpose

Define externalization protocols and provide implementations.

Mnemonic

Basic Standard Library eXternalization (bslx)

Description

The 'bslx' package defines (via documentation) the BDEX protocol for externalization (i.e., for an "out stream") and "unexternalization" (i.e., for an "in stream"), and provides concrete byte-array-based stream implementations of each kind of stream, including streams for testing. In general, concrete streams must be used in matched pairs, as described in more detail below; see also {Security Warning} below.

Hierarchical Synopsis

The 'bslx' package currently has 14 components having 5 levels of physical dependency. The list below shows the hierarchical ordering of the components. The order of components within each level is not architecturally significant, just alphabetical.

5. bslx_streambufinstream
bslx_testinstream
4. bslx_byteinstream
bslx_genericinstream
bslx_streambufoutstream
bslx_testoutstream
3. bslx_byteoutstream
bslx_genericoutstream
2. bslx_instreamfunctions
bslx_outstreamfunctions
bslx_testinstreamexception
1. bslx_marshallingutil
bslx_typecode
bslx_versionfunctions

Component Synopsis

bslx_byteinstream : Provide a stream class for unexternalization of fundamental types.

bslx_byteoutstream : Provide a stream class for externalization of fundamental types.

bslx_genericinstream : Unexternalization of fundamental types from a parameterized stream.

bslx_genericoutstream : Externalization of fundamental types to a parameterized stream.

bslx_instreamfunctions : Facilitate uniform unexternalization of user and fundamental types.

bslx_marshallingutil : Support platform-independent marshalling of fundamental types.

bslx_outstreamfunctions : Facilitate uniform externalization of user and fundamental types.

bslx_streambufinstream : Unexternalization of fundamental types from a bsl::streambuf.

bslx_streambufoutstream : Externalization of fundamental types to a bsl::streambuf.

bslx_testinstream : Enable unexternalization of fundamental types with identification.

bslx_testinstreamexception : Provide an exception class for unexternalization operations.

bslx_testoutstream : Enable externalization of fundamental types with identification.

bslx_typecode : Enumerate the fundamental types supported by BDEX.

bslx_versionfunctions : Provide functions to return BDEX version information for types.

Security Warning

Warning: BDEX is not a secure protocol. In particular, data purported to be in BDEX format should be streamed in (i.e., via a BDEX input stream) only when provided by a trusted source. 'bslx' is a low-level facility for externalizing and unexternalizing data represented in C++ objects. 'bslx' natively provides support for externalizing fundamental types, arrays, and critical Standard Library types ('bsl::string' and 'bsl::vector'); higher-level user-defined types (i.e., classes and 'struct's) implement the BDEX concepts on their own, and are responsible for performing validation when data is streamed in from a BDEX byte stream. Any input validation for higher-level types is to be implemented in those types themselves – i.e., there is no central facility for validating input – so the strength of the validation performed on an input stream is determined by the strength of the validation for all of the individual types that will process input from the stream.

Externalization

Externalization is the process of creating another representation for an in-memory object (also referred to as an "in-core" object) that can be, but need not be, stored external to processor memory. Often this is done by streaming the object as a sequence (or array) of bytes, sometimes called "flattening" the object, because of the one-dimensional structure of a sequence or array. Such flattening allows easy externalization of the object, since a byte sequence can be written to a disk file without further modification. It is similarly the native format for other externalization mechanisms, such as OS sockets, and in conjunction with these can be used to stream the object outside of processor memory. Other externalizations include storing the relevant data members among various tables and fields of a relational database.

The 'bslx' streams provide better support for externalization than 'iostream' objects because BDEX specifies a canonical, optimized representation for fundamental types, provides component authors the tools to externalize in a platform-neutral way any in-core object, and allows versioning of types not directly supported by BDEX.

When externalizing data, the version to be used must be supplied to the objects directly serialized (objects nested within these "top-level" objects obtain their version from the parent object explicitly), and this version is typically externalized as well. Likewise, the unexternalization process typically obtains the version information from the data for the top-level objects and the implementation of these top-level objects provides the corresponding version information for nested objects.

As such, any implementation of 'bdexStreamOut' is required to use only the methods provided by the BDEX-compliant stream and the methods defined in 'bslx::OutStreamFunctions' that require a version to be specified. For externalization of types not needing a version, the value 'bslx::VersionFunctions::k_NO_VERSION' should be supplied for this parameter.

However, when using 'operator<<' it is impractical to directly supply the version to be used with each top-level object. As such, an indirect method of versioning is employed, which incorporates data provided to the stream during the stream's construction, the 'versionSelector'. One requirement of all BDEX-compliant serializable types is to implement the 'maxSupportedBdexVersion' method, which converts this 'versionSelector' to the needed version on a per object-type basis. While the list of versions supported by an object is typically a sequential set of numbers starting with 1, the 'versionSelector' is expected to be formatted as "YYYYMMDD", a date representation. For example, an integral 'versionSelector' value of 20140402 represents the date 2014/04/02 (April 2, 2014).

If a top-level object is of a type directly supported by the BDEX-compliant stream, no version is externalized for the data. For the stream-supported arrays, no version is externalized and the unexternalization of this data must use the corresponding stream-supported array unexternalization method. All 'vector' externalizations include a version, which, for directly supported types, is explicitly the value 1. For nested vectors, the most-nested type is used to determine the version. If this type is directly supported by the stream, the value 1 is used; otherwise, the 'maxSupportedBdexVersion' method provided for that type is used to obtain the version information.

Supported Types

The supported types and required content are listed in the table below. All of the fundamental types in the table may be streamed as scalar values or as homogeneous arrays. 'bsl::string' is streamed as an 'int' representing the string's length and a homogeneous 'char' array for the string's data. Note that 'Int64' and 'Uint64' denote 'bsls::Types::Int64' and 'bsls::Types::Uint64', respectively, which in turn are 'typedef' names for the signed and unsigned 64-bit integer types, respectively, on the host platform:

C++ TYPE REQUIRED CONTENT OF ANY PLATFORM-NEUTRAL FORMAT
-------------- -----------------------------------------------
Int64 64 bits (signed)
Uint64 64 bits (unsigned)
int 32 bits (signed)
unsigned int 32 bits (unsigned)
short 16 bits (signed)
unsigned short 16 bits (unsigned)
char 8 bits (platform-dependent)
signed char 8 bits (signed)
unsigned char 8 bits (unsigned)
double IEEE standard 8-byte floating-point value
float IEEE standard 4-byte floating-point value
bsl::string BDE implementation of the STL string class
Definition bslstl_string.h:1281

BDEX also supports compact streaming of integer types. In particular, 64-bit integers can be streamed as 40-, 48-, 56-, or 64-bit values, and 32-bit integers can be streamed as 24- or 32-bit values, at the user's discretion. In all cases, the least-significant bytes of the fundamental integer type are written to the stream. Therefore, outputting a signed value may not preserve the sign of the original value; it is the user's responsibility to choose output methods appropriate to the data. On input, however, the non-standard bit patterns are sign-extended, so that correctly-written values will always be correctly read.

The BDEX Protocols

The BDEX protocols are primarily "documentation-only" protocols whereby BDEX-compliant value-semantic types and streams each adhere to a published documentation standard (this document) in order to interoperate correctly. The protocols specify what types that wish to support BDEX streaming must provide (three specifically-named methods), and what services the type can expect from all compliant streams (various "put" and "get" methods). In addition, BDEX also documents two interfaces, 'InStream' and 'OutStream', that serve as the "documentation protocols" for input and output streams, respectively.

Requirements for a BDEX-Compliant Class to be Streamable

In this section we give a brief synopsis of the required member functions for a class in order to be BDEX-streamable. See the "Using BDEX with Your Own Class" section below for implementation details.

The required signatures and typical documentation (some behavioral details may be implementation-specific) of the three required methods for a BDEX-compliant class are as follows:

// CLASS METHODS
static int maxSupportedBdexVersion(int versionSelector);
// Return the maximum valid BDEX format version, as indicated by the
// specified 'versionSelector', to be passed to the 'bdexStreamOut'
// method. Note that it is highly recommended that 'versionSelector' be
// formatted as "YYYYMMDD", a date representation. Also note that
// 'versionSelector' should be a *compile*-time-chosen value that selects
// a format version supported by both externalizer and unexternalizer.
// See the 'bslx' package-level documentation for more information on
// BDEX streaming of value-semantic types and containers.
// MANIPULATORS
template <class STREAM>
STREAM& bdexStreamIn(STREAM& stream, int version);
// Assign to this object the value read from the specified input 'stream'
// using the specified 'version' format, and return a reference to
// 'stream'. If 'stream' is initially invalid, this operation has no
// effect. If 'version' is not supported, this object is unaltered and
// 'stream' is invalidated, but otherwise unmodified. If 'version' is
// supported but 'stream' becomes invalid during this operation, this
// object has an undefined, but valid, state. Note that no version is
// read from 'stream'. See the 'bslx' package-level documentation for
// more information on BDEX streaming of value-semantic types and
// containers.
// ACCESSORS
template <class STREAM>
STREAM& bdexStreamOut(STREAM& stream, int version) const;
// Write the value of this object, using the specified 'version' format,
// to the specified output 'stream', and return a reference to 'stream'.
// If 'stream' is initially invalid, this operation has no effect. If
// 'version' is not supported, 'stream' is invalidated, but otherwise
// unmodified. Note that 'version' is not written to 'stream'. See the
// 'bslx' package-level documentation for more information on BDEX
// streaming of value-semantic types and containers.

Selection of Streams

At present, there are two pairs of concrete BDEX-compliant streams in 'bslx':

Out Stream In Stream Informal Designation
------------------- ------------------ --------------------
Definition bslx_byteinstream.h:394
Definition bslx_byteoutstream.h:212
Definition bslx_testinstream.h:408
Definition bslx_testoutstream.h:220

The informal designations are used throughout this document.

In general, the concrete "in streams" and "out streams" must be used in matched pairs. For example, the user should not expect correct behavior if an object is externalized to a 'bslx::TestOutStream' and then unexternalized from a seemingly-appropriately-constructed 'bslx::ByteInStream'. Each pair of streams is designed with different aims in mind, and so their exact formats may vary.

The typical user will probably be content to use the production streams for most purposes. We will assume that the production stream is the "correct" choice without further explicit discussion in most usage examples. See the individual stream component documentation for specific details about using test streams. The test streams are meant for testing only.

Using BDEX with Your Own Class

We will show a very brief example of a fictitious 'MyPoint' class whose intended purpose is to hold a pair of 'int' values representing a point in a two-dimensional rectilinear coordinate space. We will first define the class without BDEX support and then add that support. Note that, in this example, most of the required documentation and some required methods and free operators are omitted for ease of viewing.

A simple implementation of 'MyPoint' might be:

class MyPoint {
int d_x;
int d_y;
public:
// CREATORS
MyPoint() : d_x(0), d_y(0) { }
MyPoint(int x, int y) : d_x(x), d_y(y) { }
MyPoint(const MyPoint& original)
: d_x(original.d_x), d_y(original.d_y) { }
~MyPoint() { }
// MANIPULATORS
MyPoint& operator=(const MyPoint& rhs)
{ d_x = rhs.d_x; d_y = rhs.d_y; return *this; }
void setX(int x) { d_x = x; }
void setY(int y) { d_y = y; }
// ACCESSORS
int x() const { return d_x; }
int y() const { return d_y; }
};

Putting other design decisions to one side for this discussion, we may ask: How would we incorporate BDEX streaming into such a class? We observe that the actual data footprint of such a point class is two 'int' values. If BDEX succeeds in externalizing these two 'int' values (preserving their order), then the task is accomplished.

The function-level documentation should make the purpose of each method clear, and we will show the implementations for 'MyPoint' soon, but first let's just say a few words about "version". In a nutshell, the version is set to 1 in the initial release of the class, and in the best of all worlds, the version stays 1 forever. If, however, for some reason the developer wishes to alter the BDEX streaming contract (e.g., for some performance reasons), the explicit version maintains backward compatibility.

Adding the three methods to 'MyPoint' that are required for BDEX-compliance is straightforward:

class MyPoint {
int d_x;
int d_y;
public:
// CLASS METHODS
static int maxSupportedBdexVersion(int versionSelector);
// Return the maximum valid BDEX format version, as indicated by the
// specified 'versionSelector', to be passed to the 'bdexStreamOut'
// method. Note that it is highly recommended that
// 'versionSelector' be formatted as "YYYYMMDD", a date
// representation. Also note that 'versionSelector' should be a
// *compile*-time-chosen value that selects a format version
// supported by both externalizer and unexternalizer. See the
// 'bslx' package-level documentation for more information on BDEX
// streaming of value-semantic types and containers.
// CREATORS
MyPoint() : d_x(0), d_y(0) { }
MyPoint(int x, int y) : d_x(x), d_y(y) { }
MyPoint(const MyPoint& original)
: d_x(original.d_x), d_y(original.d_y) { }
~MyPoint() { }
// MANIPULATORS
MyPoint& operator=(const MyPoint& rhs)
{ d_x = rhs.d_x; d_y = rhs.d_y; return *this; }
void setX(int x) { d_x = x; }
void setY(int y) { d_y = y; }
template <class STREAM>
STREAM& bdexStreamIn(STREAM& stream, int version);
// Assign to this object the value read from the specified input
// 'stream' using the specified 'version' format, and return a
// reference to 'stream'. If 'stream' is initially invalid, this
// operation has no effect. If 'version' is not supported, this
// object is unaltered and 'stream' is invalidated, but otherwise
// unmodified. If 'version' is supported but 'stream' becomes
// invalid during this operation, this object has an undefined, but
// valid, state. Note that no version is read from 'stream'. See
// the 'bslx' package-level documentation for more information on
// BDEX streaming of value-semantic types and containers.
// ACCESSORS
int x() const { return d_x; }
int y() const { return d_y; }
template <class STREAM>
STREAM& bdexStreamOut(STREAM& stream, int version) const;
// Write the value of this object, using the specified 'version'
// format, to the specified output 'stream', and return a reference
// to 'stream'. If 'stream' is initially invalid, this operation
// has no effect. If 'version' is not supported, 'stream' is
// invalidated, but otherwise unmodified. Note that 'version' is
// not written to 'stream'. See the 'bslx' package-level
// documentation for more information on BDEX streaming of
// value-semantic types and containers.
};
STREAM & bdexStreamOut(STREAM &stream, const DayCountConvention::Enum &value, int version)
STREAM & bdexStreamIn(STREAM &stream, DayCountConvention::Enum &variable, int version)

The implementations of the new BDEX-required methods might be as follows. The 'maxSupportedBdexVersion' method simply returns the value 1 regardless of the 'versionSelector' requested:

inline
int MyPoint::maxSupportedBdexVersion(int versionSelector)
{
return 1;
}

The 'bdexStreamOut' method is an accessor (i.e., a 'const' instance method), and is therefore a bit simpler, so we'll show that one first. Anyway, it's a bit more logical to see the output format before implementing the input format. The method is a template method parameterized by 'STREAM', and the "protocol" of that 'STREAM' must be compatible with the BDEX contract. We can therefore safely assume that the 'stream' object has the required methods. See the "The BDEX Protocols" section above for the contracts. The heart of the method is the two sequential calls to 'putInt32', which externalize the 'x' and 'y' coordinates of the point value, in that order. These two lines are all the "new" code that the developer must understand and implement. Except for changing the class name from our 'MyPoint' example, the rest of the code can be copied into the new component implementation directly. Note that this template method is implemented in the header of the component defining 'MyClass':

template <class STREAM>
STREAM& MyPoint::bdexStreamOut(STREAM& stream, int version) const
{
switch (version) {
case 1: { // Implementation-specific code goes here.
stream.putInt32(d_x);
stream.putInt32(d_y);
} break;
default: {
stream.invalidate();
} break;
}
return stream;
}

Having implemented 'bdexStreamOut', implementing 'bdexStreamIn' is extremely straightforward, involving a template member function whose body can be safely copied from this example or from any appropriate component. Note that the two sequential calls to 'getInt32' must match, in both method selection and data member order, the 'putInt32' methods used in the 'bdexStreamOut' method:

template <class STREAM>
STREAM& MyPoint::bdexStreamIn(STREAM& stream, int version)
{
if (stream) {
switch (version) {
// switch on the schema version (starting with 1)
case 1: { // Implementation-specific code goes here.
stream.getInt32(d_x);
stream.getInt32(d_y);
} break;
default: {
stream.invalidate();
} break;
}
}
return stream;
}

The above implementation is sufficient for our point class, and with a very few additional considerations, illustrates the general recipe for incorporating BDEX streaming into a class that has an externalizable value.

Very briefly, we will mention two considerations that may be important when implementing a type that is more complicated than our simple point class.

For our first consideration, notice that, for our simple point class, any pattern of bits within the two 'int' data members represents a valid value. However, in general, since we require the state of an object to be valid in the face of a stream error (e.g., an exception being thrown during streaming in), the manipulator method 'bdexStreamIn' must validate the input data, set the object to some valid state in the case of an error, and invalidate the stream before returning.

The second consideration is that if the new type being implemented has as a data member a type that is already BDEX-compliant, the new implementation would use that data member's BDEX methods rather than the stream's methods directly. This is important for encapsulation.

Recommended Selection of versionSelector

BDEX provides two concepts that support versioning the BDEX serialization format of a type: 'version' and 'versionSelector'. A 'version' is a 1-based integer indicating one of the supported formats (e.g., format 1, format 2, etc.). A 'versionSelector' is a value that is mapped to a 'version' for a type by the type's implementation of 'maxSupportedBdexVersion'.

Selecting a value for a 'versionSelector' is required at two different points: (1) when implementing a new 'version' format within the 'bdexStreamIn' and 'bdexStreamOut' methods of a type, and (2) when implementing code that constructs a BDEX 'OutStream'. In both cases, the value should be a compile-time-selected value.

When a new 'version' format is implemented within the 'bdexStreamIn' and 'bdexStreamOut' methods of a type, a new mapping in 'maxSupportedBdexVersion' should be created to expose this new 'version' with a 'versionSelector'. A simple - and the recommended - approach is to use a value having the pattern "YYYYMMDD", where "YYYYMMDD" corresponds to the "go-live" date of the corresponding 'version' format.

When constructing an 'OutStream', a simple approach is to use the current date as a compile-time constant value (but see {Updating Production Systems}). In combination with the recommended selection of 'versionSelector' values for 'maxSupportedBdexVersion', this will result in consistent and predictable behavior while externalizing types. Note that this recommendation is chosen for its simplicity: to ensure the largest possible audience for an externalized representation, clients can select the minimum date value that will result in the desired version of all types externalized with 'operator<<' being selected.

Clients streaming one or more objects with BDEX create a stream and supply a 'versionSelector':

MyObject foo( /* some value */ );
bslx::ByteOutStream stream(20140725); // The minimum date that will
// result in all streamed types
// using the correct versions
// during externalization.
stream << foo;

Notice that the 'versionSelector' is a compile-time-selected value (in this case, the minimum date that will result in all streamed types using the correct versions during externalization) that can be mapped to the serialization format version of the types being serialized. The receiver of this information must support all these versions as well. Specifying future dates or allowing a run-time selection of 'versionSelector' is error prone: tasks exchanging serialized data are often compiled and deployed at different times, which would result in serialization errors if they were selecting a serialization version at run-time (there is no guarantee the receiver has been rebuilt to accept the updated format version).

For an example, assume the 'MyPoint' class is determined to need 64-bit storage for the coordinate values. The new 'bdexStreamIn' and 'bdexStreamOut' code might be implemented as:

template <class STREAM>
STREAM& MyPoint::bdexStreamIn(STREAM& stream, int version)
{
if (stream) {
switch (version) {
// switch on the schema version (starting with 1)
case 2: { // Implementation-specific code goes here.
stream.getInt64(d_x);
stream.getInt64(d_y);
} break;
case 1: { // NOTE: 'd_x' and 'd_y' were switched to 64-bit
int tmp;
stream.getInt32(tmp);
d_x = static_cast<bsls::Types::Int64>(tmp);
stream.getInt32(tmp);
d_y = static_cast<bsls::Types::Int64>(tmp);
} break;
default: {
stream.invalidate();
} break;
}
}
return stream;
}
template <class STREAM>
STREAM& MyPoint::bdexStreamOut(STREAM& stream, int version) const
{
switch (version) {
case 2: {
stream.putInt64(d_x);
stream.putInt64(d_y);
} break;
case 1: { // NOTE: 'd_x' and 'd_y' were switched to 64-bit
stream.putInt32(static_cast<int>(d_x));
stream.putInt32(static_cast<int>(d_y));
} break;
default: {
stream.invalidate();
} break;
}
return stream;
}
long long Int64
Definition bsls_types.h:132

The corresponding 'maxSupportedBdexVersion', where 2014/04/02 is the date on which the new version is introduced, might look something like:

inline
int MyPoint::maxSupportedBdexVersion(int versionSelector)
{
if (versionSelector >= 20140402) {
return 2; // RETURN
}
return 1;
}