BDE 4.14.0 Production release
Loading...
Searching...
No Matches
bdlma_managedallocator

Detailed Description

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:391

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
class my_BufferAllocator : public bdlma::ManagedAllocator {
// This 'class' provides a concrete buffer allocator that implements
// the 'bdlma::ManagedAllocator' protocol.
// 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
my_BufferAllocator(char *buffer, bsls::Types::size_type bufferSize);
// Create a buffer allocator for allocating maximally-aligned
// memory blocks from the specified external 'buffer' having the
// specified 'bufferSize' (in bytes).
~my_BufferAllocator();
// Destroy this buffer allocator.
// MANIPULATORS
void *allocate(bsls::Types::size_type size);
// 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 deallocate(void *address);
// This method has no effect for this buffer allocator.
void release();
// Release all memory allocated through this object. This
// allocator is reset to the state it was in immediately following
// construction.
};
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:124
std::ptrdiff_t IntPtr
Definition bsls_types.h:130

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
static
void *allocateFromBufferImp(bsls::Types::IntPtr *cursor,
char *buffer,
// 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'.
{
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:408
@ BSLS_MAX_ALIGNMENT
Definition bsls_alignmentutil.h:275

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:1210
Definition bslstl_vector.h:1025

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:343

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:

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

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

static
void loadIndex(SecurityCollection *securities,
bdlma::ManagedAllocator *managedAllocator,
const IndexAttributes& 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 processIndex(const SecurityCollection& securities,
const IndexAttributes& index);
// Process the specified collection of 'securities' that comprise the
// specified market '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:

int calculateMaxBufferSize(const IndexCollection& indices);
// Return the maximum buffer size (in bytes) required to process the
// specified collection of market '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:457
virtual void * allocate(size_type size)=0
static Allocator * defaultAllocator()
Definition bslma_default.h:889

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

static
void processIndices(bdlma::ManagedAllocator *managedAllocator,
const IndexCollection& indices)
// Process the specified market 'indices' using the specified
// 'managedAllocator' to supply memory.
{
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.

}