BDE 4.14.0 Production release
|
Provide an indexed set of buffers from multiple sources.
This component provides an indexed sequence (bdlbb::Blob
) of bdlbb::BlobBuffer
objects allocated from potentially multiple bdlbb::BlobBufferFactory
objects. A bdlbb::BlobBuffer
is a simple in-core value object owning a shared pointer to a memory buffer. Therefore, the lifetime of the underlying memory is determined by shared ownership between the blob buffer, the blob(s) that may contain it, and any other entities that may share ownership of the memory buffer.
Logically, a bdlbb::Blob
can be thought of as a sequence of bytes (although not contiguous). Each buffer in a blob contributes its own size to the blob, with the total size of a blob being the sum of sizes over all its buffers. A prefix of these bytes, collectively referred to as the data of the blob, are defined by the data length, which can be set by the user using the setLength
method. Note that the data length never exceeds the total size. When setting the length to a value greater than the total size, the latter is increased automatically by adding buffers created from a factory passed at construction; the behavior is undefined if no factory was supplied at construction.
The blob also updates its data length during certain operations (e.g., insertion/removal/replacement of buffers containing some data bytes), as well as several attributes driven by the data length. The first bytes numbered by the data length belong to the data buffers. Note that all data buffers, except perhaps the last, contribute all their bytes to the bdlbb::Blob
data. The last data buffer contributes anywhere between one and all of its bytes to the bdlbb::Blob
data. The number of data buffers (returned by the numDataBuffers
method), as well as the last data buffer length (returned by lastDataBufferLength
), are maintained by bdlbb::Blob
automatically when setting the length to a new value.
Buffers which do not contain data are referred to as capacity buffers. The total size of a blob does not decrease when setting the length to a value smaller than the current length. Instead, any data buffer that no longer contains data after the call to setLength
becomes a capacity buffer, and may become a data buffer again later if setting length past its prefix size.
This design is intended to allow very efficient re-assignment of buffers (or part of buffers using shared pointer aliasing) between different blobs, without copying of the underlying data, while promoting efficient allocation of resources (via retaining capacity). Thus, bdlbb::Blob
is an advantageous replacement for bdlbb::PooledBufferChain
when manipulation of the sequence, sharing of portions of the sequence, and lifetime management of individual portions of the sequence, are desired. Another added flexibility of bdlbb::Blob
is the possibility for buffers in the sequence to have different sizes (as opposed to a uniform fixed size for bdlbb::PooledBufferChain
). When choosing whether to use a bdlbb::Blob
vs. a bdlbb::PooledBufferChain
, one must consider the added flexibility versus the added cost of shared ownership for each individual buffer and random access to the buffer.
Different instances of the classes defined in this component can be concurrently modified by different threads. Thread safety of a particular instance is not guaranteed, and therefore must be handled by the user.
This section illustrates intended use of this component.
Classes that implement the bdlbb::BlobBufferFactory
protocol are used to allocate bdlbb::BlobBuffer
objects. A simple implementation follows:
Note that should the user desire a blob buffer factory for his/her application, a better implementation that pools buffers is available in the bdlbb_pooledblobbufferfactory component.
Blobs can be created just by passing a factory that is responsible to allocate the bdlbb::BlobBuffer
. The following simple program illustrates how.
Users need to access buffers directly in order to read/write data.
A bdlbb::BlobBuffer
can easily be re-assigned from one blob to another with no copy. In that case, the memory held by the buffer will be returned to its factory when the last blob referencing the buffer is destroyed. For the following example, a blob will be created using the default constructor. In this case, the bdlbb::Blob
object will not able to grow on its own. Calling setLength
for a number equal or greater than totalSize()
will result in undefined behavior.
Note that at this point, the logical length (returned by length
) of this object has not changed. setLength
must be called explicitly by the user if the logical length of the bdlbb::Blob
must be changed:
Sharing only a part of a buffer is also possible through shared pointer aliasing. In the following example, a buffer that contains only bytes 11-16 from the first buffer of blob
will be appended to blob
.
There are several typical ways of manipulating a blob: the simplest lets the blob automatically manage the length, by using only prependBuffer
, appendBuffer
, and insertBuffer
. Consider the following typical utilities (these utilities are to illustrate usage, they are not meant to be copy-pasted into application programs although they can provide a foundation for application utilities):
A possible implementation using only prependBuffer
, appendBuffer
, and insertBuffer
could be as follows:
Note that the length of blob
in the above implementation is automatically incremented by prologBuffer.size()
. Consider instead:
which inserts the prologBuffer before the first buffer of blob
. This call will almost always adjust the length properly except if the length of blob
is 0 before the insertion (i.e., the message has an empty payload). In that case, the resulting blob
will still be empty after prependProlog
, which, depending on the intention of the programmer, could be intended (avoid sending empty messages) or could be (most likely) a mistake.
The composeMessage
implementation is simplified by using prependProlog
:
Note that the deleter
is used to destroy the buffers transferred by vectors
, but not the prolog buffer.
Timestamping a message is done by creating a buffer holding a timestamp, and inserting it after the prolog and before the payload of the message. Note that in typical messages, timestamps would be part of the prolog itself, so this is a somewhat contrived example for exposition only.
Now that we have fabricated the buffer holding the current data and time, we must insert it into the blob after the first buffer (i.e., before the buffer at index 1). Note however that the payload could be empty, a condition tested by the fact that there is only one data buffer in blob
. In that case, it would be a mistake to use insertBuffer
since it would not modify the length of the blob.
Note that the call to appendDataBuffer
also takes care of the possibility that the first buffer of blob
may not be full to capacity (if the length of the blob was smaller than the buffer size, only the first blob->length()
bytes would contain prolog data). In that case, that buffer is trimmed before appending the timestampBuffer
so that the first byte of the timestampBuffer
appears immediately next to the last prolog byte, and the blob length is automatically incremented by the size of the timestampBuffer
.