BDE 4.39.x Production Release
Loading...
Searching...
No Matches
bdlma_managedallocator

Detailed Description

Provide a protocol for memory allocators that support release.

Outline

Purpose

Provide a protocol for memory allocators that support release.

Classes

See also
bdlma_bufferedsequentialallocator

Description

This component provides a class, bdlma::ManagedAllocator, that extends the bslma::Allocator protocol to allocators that support the ability to release all memory currently allocated through the protocol back to the memory supplier of the derived concrete allocator object.

,-----------------------.
`-----------------------'
| release
|
v
,----------------.
( bslma::Allocator )
`----------------'
allocate
deallocate
Definition bdlma_managedallocator.h:392

Usage

This section illustrates intended use of this component.

Example 1: Implementing the bdlma::ManagedAllocator Protocol

The bdlma::ManagedAllocator interface is especially useful for allocators that are based on an underlying pooling mechanism (e.g., bdlma::Multipool or bdlma::BufferedSequentialPool). In particular, such an allocator that implements the bdlma::ManagedAllocator interface can release, via the release method, all outstanding (pooled) memory back to the underlying allocator making the memory available for subsequent reuse. Moreover, use of the release method can also often render superfluous the running of destructors on the objects making use of a managed allocator. In this first usage example, we define the my_BufferAllocator class, an allocator that implements the bdlma::ManagedAllocator interface. my_BufferAllocator is a considerably pared down version of bdlma::BufferedSequentialAllocator, and is intended for illustration purposes only. Please see the bdlma_bufferedsequentialallocator component for full documentation of bdlma::BufferedSequentialAllocator, a managed allocator meant for production use.

First, we define the interface of the my_BufferAllocator class:

// my_bufferallocator.h
/// This `class` provides a concrete buffer allocator that implements
/// the `bdlma::ManagedAllocator` protocol.
class my_BufferAllocator : public bdlma::ManagedAllocator {
// DATA
char *d_buffer_p; // external buffer (held, not
// owned)
bsls::Types::size_type d_bufferSize; // size (in bytes) of external
// buffer
bsls::Types::IntPtr d_cursor; // offset to next available byte
// in buffer
private:
// NOT IMPLEMENTED
my_BufferAllocator(const my_BufferAllocator&);
my_BufferAllocator& operator=(const my_BufferAllocator&);
public:
// CREATORS
/// Create a buffer allocator for allocating maximally-aligned
/// memory blocks from the specified external `buffer` having the
/// specified `bufferSize` (in bytes).
my_BufferAllocator(char *buffer, bsls::Types::size_type bufferSize);
/// Destroy this buffer allocator.
~my_BufferAllocator();
// MANIPULATORS
/// Return the address of a maximally-aligned contiguous block of
/// memory of the specified `size` (in bytes) on success, and 0 if
/// the allocation request exceeds the remaining free memory space
/// in the external buffer.
void *allocate(bsls::Types::size_type size);
/// This method has no effect for this buffer allocator.
void deallocate(void *address);
/// Release all memory allocated through this object. This allocator
/// is reset to the state it was in immediately following construction.
void release();
};
memory_resource & operator=(const memory_resource &) BSLS_KEYWORD_DEFAULT
Return a modifiable reference to this object.
std::size_t size_type
Definition bsls_types.h:126
std::ptrdiff_t IntPtr
Definition bsls_types.h:132

Next, we define the inline methods of my_BufferAllocator. Note that the release method resets the internal cursor to 0, effectively making the memory from the entire external buffer supplied at construction available for subsequent allocations, but has no effect on the contents of the buffer:

// CREATORS
inline
my_BufferAllocator::my_BufferAllocator(char *buffer,
: d_buffer_p(buffer)
, d_bufferSize(bufferSize)
, d_cursor(0)
{
}
// MANIPULATORS
inline
void my_BufferAllocator::deallocate(void *)
{
}
inline
void my_BufferAllocator::release()
{
d_cursor = 0;
}

Finally, we provide the implementation of the my_BufferAllocator methods that are defined in the .cpp file. A static helper function, allocateFromBufferImp, provides the bulk of the implementation of the allocate method:

// my_bufferallocator.cpp
// STATIC HELPER FUNCTIONS
/// Allocate a maximally-aligned memory block of the specified `size`
/// (in bytes) from the specified `buffer` having the specified
/// `bufferSize` (in bytes) at the specified `cursor` position. Return
/// the address of the allocated memory block if `buffer` contains
/// sufficient available memory, and 0 otherwise. The `cursor` is set
/// to the first byte position immediately after the allocated memory if
/// there is sufficient memory, and not modified otherwise. The
/// behavior is undefined unless `0 < size`, `0 <= *cursor`, and
/// `*cursor <= bufferSize`.
static
void *allocateFromBufferImp(bsls::Types::IntPtr *cursor,
char *buffer,
{
buffer + *cursor,
if (*cursor + offset + size > bufferSize) { // insufficient space
return 0; // RETURN
}
void *result = &buffer[*cursor + offset];
*cursor += offset + size;
return result;
}
// CREATORS
my_BufferAllocator::~my_BufferAllocator()
{
}
// MANIPULATORS
void *my_BufferAllocator::allocate(bsls::Types::size_type size)
{
return 0 == size ? 0 : allocateFromBufferImp(&d_cursor,
d_buffer_p,
d_bufferSize,
static_cast<int>(size));
}
bsl::size_t size(const TYPE &array)
Return the number of elements in the specified array.
static int calculateAlignmentOffset(const void *address, int alignment)
Definition bsls_alignmentutil.h:462
@ BSLS_MAX_ALIGNMENT
Definition bsls_alignmentutil.h:300

Example 2: Using the bdlma::ManagedAllocator Protocol

In this second usage example, we illustrate how the managed allocator that was defined in Example 1, my_BufferAllocator, may be used. Note that substantial portions of the sample implementation are elided as they would only add unnecessary complications to the usage example. The portions shown are sufficient to illustrate the use of bdlma::ManagedAllocator.

The domain of our example is financial markets. Suppose that we are given a list of market indices (e.g., Dow Jones Industrial Average, S&P 500, etc.), and we want to perform some computation on each index, in turn. In this example, the essential attributes of an index are held in a bsl::pair consisting of the name of the index (e.g., "DJIA") and the number of securities that comprise the index (e.g., 30 in the case of the DJIA). The collection of market indices that we wish to process is given by a vector of such pairs. Thus, we make use of these types related to indices:

typedef bsl::pair<const char *, int> IndexAttributes;
typedef bsl::vector<IndexAttributes> IndexCollection;
Definition bslstl_pair.h:1280
Definition bslstl_vector.h:1120

In our example, a security is defined by the unconstrained attribute type my_SecurityAttributes, the interface and implementation of which is elided except we note that it uses bslma allocators:

class my_SecurityAttributes {
// ...
public:
// TRAITS
BSLMF_NESTED_TRAIT_DECLARATION(my_SecurityAttributes,
// ...
};
#define BSLMF_NESTED_TRAIT_DECLARATION(t_TYPE, t_TRAIT)
Definition bslmf_nestedtraitdeclaration.h:231
Definition bslma_usesbslmaallocator.h:344

For the collection of securities comprising an index we use a vector of my_SecurityAttributes:

typedef bsl::vector<my_SecurityAttributes> SecurityCollection;

Since some indices are quite large (e.g., Russell 3000, Wilshire 5000), for performance reasons it is advantageous for a SecurityCollection to use an efficient memory allocation strategy. This is where my_BufferAllocator comes into play, which we will see shortly.

The top-level function in our example takes a bdlma::ManagedAllocator * and the collection of market indices that we wish to process:

/// Process the specified market `indices` using the specified
/// `managedAllocator` to supply memory.
static
void processIndices(bdlma::ManagedAllocator *managedAllocator,
const IndexCollection& indices);

processIndices makes use of two helper functions to process each index:

/// Load into the specified collection of `securities` the attributes of
/// the securities comprising the specified market `index` using the
/// specified `managedAllocator` to supply memory.
static
void loadIndex(SecurityCollection *securities,
bdlma::ManagedAllocator *managedAllocator,
const IndexAttributes& index);
/// Process the specified collection of `securities` that comprise the
/// specified market `index`.
static
void processIndex(const SecurityCollection& securities,
const IndexAttributes& index);

Since we plan to use my_BufferAllocator as our managed allocator, we need to supply it with an external buffer. The calculateMaxBufferSize function computes the size of the buffer required to store the SecurityCollection corresponding to the largest index to be processed by a given call to processIndices:

/// Return the maximum buffer size (in bytes) required to process the
/// specified collection of market `indices`.
int calculateMaxBufferSize(const IndexCollection& indices);

Before showing the implementation of processIndices, where the most interesting use of our managed allocator takes place, we show the site of the call to processIndices.

First, assume that we have been given an IndexCollection that has been populated with one or more IndexAttributes:

IndexCollection indices; // assume populated

Next, we calculate the size of the buffer that is needed, allocate the memory for the buffer from the default allocator, create our concrete managed allocator (namely, an instance of my_BufferAllocator), and call processIndices:

const int bufferSize = calculateMaxBufferSize(indices);
char *buffer = static_cast<char *>(allocator->allocate(bufferSize));
my_BufferAllocator bufferAllocator(buffer, bufferSize);
processIndices(&bufferAllocator, indices);
Definition bslma_allocator.h:545
virtual void * allocate(size_type size)=0
static Allocator * defaultAllocator()
Definition bslma_default.h:905

Next, we show the implementation of processIndices, within which we iterate over the market indices that are passed to it:

/// Process the specified market `indices` using the specified
/// `managedAllocator` to supply memory.
static
void processIndices(bdlma::ManagedAllocator *managedAllocator,
const IndexCollection& indices)
{
for (IndexCollection::const_iterator citer = indices.begin();
citer != indices.end(); ++citer) {

For each index, the SecurityCollection comprising that index is created. All of the memory needs of the SecurityCollection are provided by the managedAllocator. Note that even the memory for the footprint of the collection comes from the managedAllocator:

SecurityCollection *securities =
new (managedAllocator->allocate(sizeof(SecurityCollection)))
SecurityCollection(managedAllocator);

Next, we call loadIndex to populate securities, followed by the call to processIndex. loadIndex also uses the managedAllocator, the details of which are not shown here:

loadIndex(securities, managedAllocator, *citer);
processIndex(*securities, *citer);

After the index is processed, release is called on the managed allocator making all of the buffer supplied to the allocator at construction available for reuse:

managedAllocator->release();
}
virtual void release()=0

Finally, we let the SecurityCollection used to process the index go out of scope intentionally without deleting securities. The call to release renders superfluous the need to call the SecurityCollection destructor as well as the destructor of the contained my_SecurityAttributes elements.

}