Quick Links:

bal | bbl | bdl | bsl

Namespaces

Component bdlma_managedallocator
[Package bdlma]

Provide a protocol for memory allocators that support release. More...

Namespaces

namespace  bdlma

Detailed Description

Outline
Purpose:
Provide a protocol for memory allocators that support release.
Classes:
bdlma::ManagedAllocator protocol for allocators with release capability
See also:
Component 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.
   ,-----------------------.
  ( bdlma::ManagedAllocator )
   `-----------------------'
               |        release
               |
               v
       ,----------------.
      ( bslma::Allocator )
       `----------------'
                        allocate
                        deallocate
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.
  };
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,
                                         bsls::Types::size_type  bufferSize)
  : 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,
                              bsls::Types::size_type  bufferSize,
                              bsls::Types::size_type  size)
      // 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'.

  {
      const int offset = bsls::AlignmentUtil::calculateAlignmentOffset(
                                    buffer + *cursor,
                                    bsls::AlignmentUtil::BSLS_MAX_ALIGNMENT);

      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));
  }
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;
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,
                                     bslma::UsesBslmaAllocator);

      // ...
  };
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);

  bslma::Allocator *allocator = bslma::Default::defaultAllocator();
  char *buffer = static_cast<char *>(allocator->allocate(bufferSize));

  my_BufferAllocator bufferAllocator(buffer, bufferSize);

  processIndices(&bufferAllocator, indices);
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();
      }
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.
  }