BDE 4.14.0 Production release
|
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. | |
Define externalization protocols and provide implementations.
Basic Standard Library eXternalization (bslx)
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.
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.
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.
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 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.
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:
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 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.
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:
At present, there are two pairs of concrete BDEX-compliant streams in 'bslx':
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.
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:
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:
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:
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':
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:
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.
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':
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:
The corresponding 'maxSupportedBdexVersion', where 2014/04/02 is the date on which the new version is introduced, might look something like: