// bdlma_multipoolallocator.h                                         -*-C++-*-
#ifndef INCLUDED_BDLMA_MULTIPOOLALLOCATOR
#define INCLUDED_BDLMA_MULTIPOOLALLOCATOR

#include <bsls_ident.h>
BSLS_IDENT("$Id: $")

//@PURPOSE: Provide a memory-pooling allocator of heterogeneous block sizes.
//
//@CLASSES:
//  bdlma::MultipoolAllocator: allocator managing varying-size memory pools
//
//@SEE_ALSO: bdlma_pool, bdlma_multipool
//
//@DESCRIPTION: This component provides a general-purpose, managed allocator,
// `bdlma::MultipoolAllocator`, that implements the `bdlma::ManagedAllocator`
// protocol and provides an allocator that maintains a configurable number of
// `bdlma::Pool` objects, each dispensing maximally-aligned memory blocks of a
// unique size.  The `bdlma::Pool` objects are placed in an array, starting at
// index 0, with each successive pool managing memory blocks of a size twice
// that of the previous pool.  Each multipool allocation (deallocation) request
// allocates memory from (returns memory to) the internal pool managing memory
// blocks of the smallest size not less than the requested size, or else from a
// separately managed list of memory blocks, if no internal pool managing
// memory blocks of sufficient size exists.  Both the `release` method and the
// destructor of a `bdlma::MultipoolAllocator` release all memory currently
// allocated via the object.
// ```
//  ,-------------------------.
// ( bdlma::MultipoolAllocator )
//  `-------------------------'
//               |         ctor/dtor
//               |         maxPooledBlockSize
//               |         numPools
//               |         reserveCapacity
//               V
//   ,-----------------------.
//  ( bdlma::ManagedAllocator )
//   `-----------------------'
//               |         release
//               V
//      ,----------------.
//     ( bslma::Allocator )
//      `----------------'
//                         allocate
//                         deallocate
// ```
// The main difference between a `bdlma::MultipoolAllocator` and a
// `bdlma::Multipool` is that, very often, a `bdlma::MultipoolAllocator` is
// managed through a `bslma::Allocator` pointer.  Hence, every call to the
// `allocate` method invokes a virtual function call, which is slower than
// invoking the non-virtual `allocate` method on a `bdlma::Multipool`.
// However, since `bslma::Allocator *` is widely used across BDE interfaces,
// `bdlma::MultipoolAllocator` is more general purpose than `bdlma::Multipool`.
//
///Configuration at Construction
///-----------------------------
// When creating a `bdlma::MultipoolAllocator`, clients can optionally
// configure:
//
// 1. NUMBER OF POOLS -- the number of internal pools (the block size managed
//    by the first pool is eight bytes, with each successive pool managing
//    blocks of a size twice that of the previous pool).
// 2. GROWTH STRATEGY -- geometrically growing chunk size starting from 1 (in
//    terms of the number of memory blocks per chunk), or fixed chunk size,
//    specified as either:
//   - the unique growth strategy for all pools, or
//   - (if the number of pools is specified) an array of growth strategies
//     corresponding to each individual pool.
//    If the growth strategy is not specified, geometric growth is used for all
//    pools.
// 3. MAX BLOCKS PER CHUNK -- the maximum number of memory blocks within a
//    chunk, specified as either:
//     - the unique maximum-blocks-per-chunk value for all of the pools, or
//     - an array of maximum-blocks-per-chunk values corresponding to each
//       individual pool.
//    If the maximum blocks per chunk is not specified, an
//    implementation-defined default value is used.  Note that the maximum
//    blocks per chunk can be configured only if the number of pools is also
//    configured.
// 4. BASIC ALLOCATOR -- the allocator used to supply memory (to replenish an
//    internal pool, or directly if the maximum block size is exceeded).  If
//    not specified, the currently installed default allocator is used (see
//    `bslma_default`).
//
// A default-constructed multipool allocator has a relatively small,
// implementation-defined number of pools, `N`, with respective block sizes
// ranging from `2^3 = 8` to `2^(N+2)`.  By default, the initial chunk size,
// (i.e., the number of blocks of a given size allocated at once to replenish a
// pool's memory) is 1, and each pool's chunk size grows geometrically until it
// reaches an implementation-defined maximum, at which it is capped.  Finally,
// unless otherwise specified, all memory comes from the allocator that was the
// currently installed default allocator at the time the
// `bdlma::MultipoolAllocator` was created.
//
// Using the various pooling options described above, we can configure the
// number of pools maintained, whether replenishment should be adaptive (i.e.,
// geometric starting with 1) or fixed at a maximum chunk size, what that
// maximum chunk size should be (which need not be an integral power of 2), and
// the underlying allocator used to supply memory.  Note that both GROWTH
// STRATEGY and MAX BLOCKS PER CHUNK can be specified separately either as a
// single value applying to all of the maintained pools, or as an array of
// values, with the elements applying to each individually maintained pool.
//
///Usage
///-----
// This section illustrates intended use of this component.
//
///Example 1: Using a `bdlma::MultipoolAllocator`
/// - - - - - - - - - - - - - - - - - - - - - - -
// A `bdlma::MultipoolAllocator` can be used to supply memory to node-based
// data structures such as `bsl::set`, `bsl::list`, and `bsl::map`.  Suppose we
// are implementing a container of named graphs, where a graph is defined by a
// set of edges and a set of nodes.  The various fixed-sized nodes and edges
// can be efficiently allocated by a `bdlma::MultipoolAllocator`.
//
// First, the edge class, `my_Edge`, is defined as follows:
// ```
// class my_Node;
//
// /// This class represents an edge within a graph.  Both ends of an edge
// /// must be connected to nodes.
// class my_Edge {
//
//     // DATA
//     my_Node *d_first;   // first node
//     my_Node *d_second;  // second node
//
//     // ...
//
//   public:
//     // CREATORS
//
//     /// Create an edge that connects to the specified `first` and
//     /// `second` nodes.
//     my_Edge(my_Node *first, my_Node *second);
//
//     // ...
// };
//
// // CREATORS
// my_Edge::my_Edge(my_Node *first, my_Node *second)
// : d_first(first)
// , d_second(second)
// {
// }
// ```
// Then, the node class, `my_Node`, is defined as follows:
// ```
// /// This class represents a node within a graph.  A node can be
// /// connected to any number of edges.
// class my_Node {
//
//     // DATA
//     bsl::set<my_Edge *> d_edges;  // set of edges this node connects to
//
//     // ...
//
//   private:
//     // Not implemented:
//     my_Node(const my_Node&);
//
//   public:
//     // TRAITS
//     BSLMF_NESTED_TRAIT_DECLARATION(my_Node, bslma::UsesBslmaAllocator);
//
//     // CREATORS
//
//     /// Create a node not connected to any other nodes.  Optionally
//     /// specify a `basicAllocator` used to supply memory.  If
//     /// `basicAllocator` is 0, the currently installed default allocator
//     /// is used.
//     explicit my_Node(bslma::Allocator *basicAllocator = 0);
//
//     // ...
// };
//
// // CREATORS
// my_Node::my_Node(bslma::Allocator *basicAllocator)
// : d_edges(basicAllocator)
// {
// }
// ```
// Then, we define the graph class, `my_Graph`, as follows:
// ```
// /// This class represents a graph having sets of nodes and edges.
// class my_Graph {
//
//     // DATA
//     bsl::set<my_Edge> d_edges;  // set of edges in this graph
//     bsl::set<my_Node> d_nodes;  // set of nodes in this graph
//
//     // ...
//
//   private:
//     // Not implemented:
//     my_Graph(const my_Graph&);
//
//   public:
//     // TRAITS
//     BSLMF_NESTED_TRAIT_DECLARATION(my_Graph, bslma::UsesBslmaAllocator);
//
//     // CREATORS
//
//     /// Create an empty graph.  Optionally specify a `basicAllocator`
//     /// used to supply memory.  If `basicAllocator` is 0, the currently
//     /// installed default allocator is used.
//     explicit my_Graph(bslma::Allocator *basicAllocator = 0);
//
//     // ...
// };
//
// my_Graph::my_Graph(bslma::Allocator *basicAllocator)
// : d_edges(basicAllocator)
// , d_nodes(basicAllocator)
// {
// }
// ```
// Next, the container for the collection of named graphs,
// `my_NamedGraphContainer`, is defined as follows:
// ```
// /// This class stores a map that indexes graph names to graph objects.
// class my_NamedGraphContainer {
//
//     // DATA
//     bsl::map<bsl::string, my_Graph> d_graphMap;  // map from graph name to
//                                                  // graph
//
//   private:
//     // NOT IMPLEMENTED
//     my_NamedGraphContainer(const my_NamedGraphContainer&);
//
//   public:
//     // TRAITS
//     BSLMF_NESTED_TRAIT_DECLARATION(my_NamedGraphContainer,
//                                    bslma::UsesBslmaAllocator);
//
//     // CREATORS
//
//     /// Create an empty named graph container.  Optionally specify a
//     /// 'basicAllocator' used to supply memory.  If 'basicAllocator' is
//     /// 0, the currently installed default allocator is used.
//     explicit my_NamedGraphContainer(bslma::Allocator *basicAllocator = 0);
//
//     // ...
// };
//
// // CREATORS
// my_NamedGraphContainer::my_NamedGraphContainer(
//                                           bslma::Allocator *basicAllocator)
// : d_graphMap(basicAllocator)
// {
// }
// ```
// Finally, in `main`, we can create a `bdlma::MultipoolAllocator` and pass it
// to our `my_NamedGraphContainer`.  Since we know that the maximum block size
// needed is 32 (`sizeof(my_Graph)`), we can calculate the number of pools
// needed by using the formula given in the "Configuration at Construction"
// section:
// ```
// largestPoolSize == 2 ^ (N + 2)
// ```
// When solved for the above equation, the smallest `N` that satisfies this
// relationship is 3:
// ```
// int main()
// {
//     enum { k_NUM_POOLS = 3 };
//
//     bdlma::MultipoolAllocator multipoolAllocator(NUM_POOLS);
//
//     my_NamedGraphContainer container(&multipoolAllocator);
// }
// ```
//
///Example 2: Performance of a `bdlma::MultipoolAllocator`
/// - - - - - - - - - - - - - - - - - - - - - - - - - - -
// A `bdlma::MultipoolAllocator` can greatly improve efficiency when it is used
// to supply memory to node-based data structures that frequently both insert
// and remove nodes, while growing to significant size before being destroyed.
// The following experiment will illustrate the benefits of using a
// `bdlma::MultipoolAllocator` under this scenario by comparing the following 3
// different allocator uses:
//
// 1. Using the `bslma::NewDeleteAllocator`.
// 2. Using a `bdlma::MultipoolAllocator` as a substitute for the
//    `bslma::NewDeleteAllocator`.
// 3. Exploiting the managed aspect of `bdlma::MultipoolAllocator` by avoiding
//    invocation of the destructor of the data structure, since the
//    destruction of the allocator will automatically reclaim all memory.
//
// First, we create a test data structure that contains three `bsl::list`s.
// Each list holds a different type of object, where each type has a different
// size.  For simplicity, we create these different object types as different
// instantiations of a template class, parameterized on the object size:
// ```
// template <int OBJECT_SIZE>
// class my_TestObject {
//
//     // DATA
//     char d_data[OBJECT_SIZE];
// };
// ```
// Again, for simplicity, the sizes of these objects are chosen to be 20, 40,
// and 80, instead of being parameterized as part of the test data structure:
// ```
// class my_TestDataStructure {
//
//     // PRIVATE TYPES
//     typedef my_TestObject<20> Obj1;
//     typedef my_TestObject<40> Obj2;
//     typedef my_TestObject<80> Obj3;
//
//     // DATA
//     bsl::list<Obj1> d_list1;
//     bsl::list<Obj2> d_list2;
//     bsl::list<Obj3> d_list3;
// ```
// The test will consist of the following steps:
//
// 1. Push back into `d_list1`, then `d_list2`, then `d_list3`.
// 2. Repeat #1.
// 3. Pop front from `d_list1`, then `d_list2`, then `d_list3`.
//
// The above 3 steps will be repeated `n` times, where `n` is a parameter
// specified by the user.  This process will both grow the list and incorporate
// a large number of node removals.  Note that nodes are removed from the front
// of the list to simulate a particular real-world usage, where nodes removed
// are rarely those recently added (this also removes the possibility of noise
// from potential optimizations with relinquishing memory blocks that are most
// recently allocated).
// ```
//   public:
//     // CREATORS
//     my_TestDataStructure(bslma::Allocator *basicAllocator = 0);
//
//     // MANIPULATORS
//     void pop();
//
//     void push();
// };
//
// // CREATORS
// my_TestDataStructure::my_TestDataStructure(
//                                           bslma::Allocator *basicAllocator)
// : d_list1(basicAllocator)
// , d_list2(basicAllocator)
// , d_list3(basicAllocator)
// {
// }
// ```
// The `push` method will push into the 3 `bsl::list` objects managed by
// `my_TestDataStructure` sequentially.  Similarly, the `pop` method will pop
// from the lists sequentially:
// ```
// // MANIPULATORS
// void my_TestDataStructure::push()
// {
//     // Push to the back of the 3 lists.
//
//     d_list1.push_back(Obj1());
//     d_list2.push_back(Obj2());
//     d_list3.push_back(Obj3());
// }
//
// void my_TestDataStructure::pop()
// {
//     // Pop from the front of the 3 lists.
//
//     d_list1.pop_front();
//     d_list2.pop_front();
//     d_list3.pop_front();
// }
// ```
// The above `push` and `pop` methods will allow us to evaluate the cost to add
// and remove nodes using different allocators.  To evaluate the cost of
// destruction (and hence deallocation of all allocated memory in the list
// objects), we supply a `static` `test` method within a `my_TestUtil` class to
// create the test mechanism, run the test, and destroy the test mechanism.
//
// The `test` method accepts a `testLengthFactor` argument specified by the
// user to control the length of the test.  The effect of `testLengthFactor` is
// shown below:
// ```
// testLengthFactor            test size           n      iterations
// ----------------     ----------------     --------     ----------
//       4                  10^4 = 10000           1          10000
//                                                10           1000
//                                               100            100
//                                              1000             10
//                                             10000              1
//
//       5                 10^5 = 100000           1         100000
//                                                10          10000
//                                               100           1000
//                                              1000            100
//                                             10000             10
//                                            100000              1
//
//       6                10^6 = 1000000           1        1000000
//                                                10         100000
//                                               100          10000
//                                              1000           1000
//                                             10000            100
//                                            100000             10
//                                           1000000              1
//
//  // ...
// ```
// For each row of the specified `testLengthFactor`, a `my_TestDataStructure`
// will be created "iterations" times, and each time the lists within the data
// structure will grow by invoking `push` twice and `pop` once.  Note that "n"
// measures the effect of insertion and removal of nodes, while "iterations"
// measures the effect of construction and destruction of entire lists of
// nodes.
//
// The `test` method also accepts a `bslma::Allocator *` to be used as the
// allocator used to construct the test mechanism and its internal lists:
// ```
// class my_TestUtil {
//
//   public:
//     // CLASS METHODS
//     static
//     void test(int testLengthFactor, bslma::Allocator *basicAllocator)
//     {
//         int n          = 1;
//         int iterations = 1;
//
//         for (int i = 0; i < testLengthFactor; ++i) {
//             iterations *= 10;
//         }
//
//         for (int i = 0; i <= testLengthFactor; ++i) {
//             bsls::Stopwatch timer;
//             timer.start();
//
//             for (int j = 0; j < iterations; ++j) {
//                 my_TestDataStructure tds(basicAllocator);
//
//                 // Testing cost of insertion and deletion.
//
//                 for (int k = 0; k < n; ++k) {
//                     tds.push();
//                     tds.push();
//                     tds.pop();
//                 }
//
//                 // Testing cost of destruction.
//             }
//
//             timer.stop();
//
//             printf("%d\t%d\t%d\t%1.6f\n", testLengthFactor,
//                                           n,
//                                           iterations,
//                                           timer.elapsedTime());
//
//             n          *= 10;
//             iterations /= 10;
//         }
//     }
// ```
// Next, to fully test the benefit of being able to relinquish all allocated
// memory at once, we use the `testManaged` method, which accepts only managed
// allocators.  Instead of creating the test mechanism on the stack, the test
// mechanism will be created on the heap.  After running the test, the
// `release` method of the allocator will reclaim all outstanding allocations
// at once, eliminating the need to run the destructor of the test mechanism:
// ```
//     static
//     void testManaged(int                      testLengthFactor,
//                      bdlma::ManagedAllocator *managedAllocator)
//     {
//         int n          = 1;
//         int iterations = 1;
//
//         for (int i = 0; i < testLengthFactor; ++i) {
//             iterations *= 10;
//         }
//
//         for (int i = 0; i <= testLengthFactor; ++i) {
//             bsls::Stopwatch timer;
//             timer.start();
//
//             for (int j = 0; j < iterations; ++j) {
//                 my_TestDataStructure *tmPtr = new(*managedAllocator)
//                                     my_TestDataStructure(managedAllocator);
//
//                 // Testing cost of insertion and deletion.
//
//                 for (int k = 0; k < n; ++k) {
//                     tmPtr->push();
//                     tmPtr->push();
//                     tmPtr->pop();
//                 }
//
//                 // Testing cost of destruction.
//
//                 managedAllocator->release();
//             }
//
//             timer.stop();
//
//             printf("%d\t%d\t%d\t%1.6f\n", testLengthFactor,
//                                           n,
//                                           iterations,
//                                           timer.elapsedTime());
//
//             n          *= 10;
//             iterations /= 10;
//         }
//     }
// };
// ```
// Finally, in main, we run the test with the different allocators and
// different allocator configurations based on command line arguments:
// ```
// {
//     int testLengthFactor = 5;
//     const int NUM_POOLS  = 10;
//
//     if (argc > 2) {
//         testLengthFactor = bsl::atoi(argv[2]);
//     }
//
//     char growth = 'g';
//     if (argc > 3) {
//         growth = argv[3][0];
//         if (growth != 'g' && growth != 'c') {
//             printf("[g]eometric or [c]onstant growth must be used\n");
//             return -1;
//         }
//     }
//
//     int maxChunkSize = 32;
//     if (argc > 4) {
//         maxChunkSize = bsl::atoi(argv[4]);
//         if (maxChunkSize <= 0) {
//             printf("maxChunkSize must be >= 1");
//         }
//     }
//
//     bsls::BlockGrowth::Strategy strategy = growth == 'g'
//                                         ? bsls::BlockGrowth::BSLS_GEOMETRIC
//                                         : bsls::BlockGrowth::BSLS_CONSTANT;
//
//     printf("\nNew Delete Allocator:\n\n");
//     {
//         bslma::Allocator *nda = bslma::NewDeleteAllocator::allocator(0);
//         my_TestUtil::test(testLengthFactor, nda);
//     }
//
//     printf("\nMultipool Allocator with [%c], [%d]:\n\n", growth,
//                                                              maxChunkSize);
//     {
//         bdlma::MultipoolAllocator ma(NUM_POOLS, strategy, maxChunkSize);
//         my_TestUtil::test(testLengthFactor, &ma);
//     }
//
//     printf("\nMultipool Allocator Managed with [%c], [%d]:\n\n", growth,
//                                                              maxChunkSize);
//     {
//         bdlma::MultipoolAllocator ma(NUM_POOLS, strategy, maxChunkSize);
//         my_TestUtil::testManaged(testLengthFactor, &ma);
//     }
//
//     return 0;
// }
// ```
// An excerpt of the results of the test running on IBM under optimized mode,
// using default constructed `bdlma::MultipoolAllocator` parameters, is shown
// below:
// ```
// New Delete Allocator:
//
// 6       1       1000000 3.006253
// 6       10      100000  2.369734
// 6       100     10000   2.598567
// 6       1000    1000    2.604546
// 6       10000   100     2.760319
// 6       100000  10      3.085765
// 6       1000000 1       4.465030
//
// Multipool Allocator with [g], [32]:
//
// 6       1       1000000 0.766064
// 6       10      100000  0.408509
// 6       100     10000   0.357019
// 6       1000    1000    0.436448
// 6       10000   100     0.643206
// 6       100000  10      0.932662
// 6       1000000 1       0.938906
//
// Multipool Allocator Managed with [g], [32]:
//
// 6       1       1000000 1.958663
// 6       10      100000  0.463185
// 6       100     10000   0.371201
// 6       1000    1000    0.357816
// 6       10000   100     0.368082
// 6       100000  10      0.388422
// 6       1000000 1       0.529167
// ```
// It is clear that using a `bdlma::MultipoolAllocator` results in an
// improvement in memory allocation by a factor of about 4.  Furthermore, if
// the managed aspect of the multipool allocator is exploited, the cost of
// destruction rapidly decreases in relative terms as the list grows larger
// (increasing `n`).

#include <bdlscm_version.h>

#include <bdlma_managedallocator.h>
#include <bdlma_multipool.h>

#include <bslma_allocator.h>

#include <bsls_keyword.h>
#include <bsls_performancehint.h>
#include <bsls_types.h>

namespace BloombergLP {
namespace bdlma {

                         // ========================
                         // class MultipoolAllocator
                         // ========================

/// This class implements the `bdlma::ManagedAllocator` protocol to provide
/// an allocator that maintains a configurable number of `bdlma::Pool`
/// objects, each dispensing memory blocks of a unique size.  The
/// `bdlma::Pool` objects are placed in an array, with each successive pool
/// managing memory blocks of size twice that of the previous pool.  Each
/// multipool allocation (deallocation) request allocates memory from
/// (returns memory to) the internal pool having the smallest block size not
/// less than the requested size, or, if no pool manages memory blocks of
/// sufficient sized, from a separately managed list of memory blocks.  Both
/// the `release` method and the destructor of a `bdlma::MultipoolAllocator`
/// release all memory currently allocated via the object.
class MultipoolAllocator : public ManagedAllocator {

    // DATA
    Multipool d_multipool;  // manager for allocated memory blocks

  private:
    // NOT IMPLEMENTED
    MultipoolAllocator(const MultipoolAllocator&);
    MultipoolAllocator& operator=(const MultipoolAllocator&);

  public:
    // CREATORS

    /// Create a multipool allocator.  Optionally specify `numPools`,
    /// indicating the number of internally created `bdlma::Pool` objects;
    /// the block size of the first pool is 8 bytes, with the block size of
    /// each additional pool successively doubling.  If `numPools` is not
    /// specified, an implementation-defined number of pools `N` -- covering
    /// memory blocks ranging in size from `2^3 = 8` to `2^(N+2)` -- are
    /// created.  Optionally specify a `growthStrategy` indicating whether
    /// the number of blocks allocated at once for every internally created
    /// `bdlma::Pool` should be either fixed or grow geometrically, starting
    /// with 1.  If `growthStrategy` is not specified, the allocation
    /// strategy for each internally created `bdlma::Pool` object is
    /// geometric, starting from 1.  If `numPools` and `growthStrategy` are
    /// specified, optionally specify a `maxBlocksPerChunk`, indicating the
    /// maximum number of blocks to be allocated at once when a pool must be
    /// replenished.  If `maxBlocksPerChunk` is not specified, an
    /// implementation-defined value is used.  Optionally specify a
    /// `basicAllocator` used to supply memory.  If `basicAllocator` is 0,
    /// the currently installed default allocator is used.  Memory
    /// allocation (and deallocation) requests will be satisfied using the
    /// internally maintained pool managing memory blocks of the smallest
    /// size not less than the requested size, or directly from the
    /// underlying allocator (supplied at construction), if no internal pool
    /// managing memory blocks of sufficient size exists.  The behavior is
    /// undefined unless `1 <= numPools` and `1 <= maxBlocksPerChunk`.  Note
    /// that, on platforms where
    /// `8 < bsls::AlignmentUtil::BSLS_MAX_ALIGNMENT`, excess memory may be
    /// allocated for pools managing smaller blocks.  Also note that
    /// `maxBlocksPerChunk` need not be an integral power of 2; if geometric
    /// growth would exceed the maximum value, the chunk size is capped at
    /// that value.
    explicit
    MultipoolAllocator(bslma::Allocator *basicAllocator = 0);
    explicit
    MultipoolAllocator(int numPools, bslma::Allocator *basicAllocator = 0);
    explicit
    MultipoolAllocator(bsls::BlockGrowth::Strategy  growthStrategy,
                       bslma::Allocator            *basicAllocator = 0);
    MultipoolAllocator(int                          numPools,
                       bsls::BlockGrowth::Strategy  growthStrategy,
                       bslma::Allocator            *basicAllocator = 0);
    MultipoolAllocator(int                          numPools,
                       bsls::BlockGrowth::Strategy  growthStrategy,
                       int                          maxBlocksPerChunk,
                       bslma::Allocator            *basicAllocator = 0);

    /// Create a multipool allocator having the specified `numPools`,
    /// indicating the number of internally created `bdlma::Pool` objects;
    /// the block size of the first pool is 8 bytes, with the block size of
    /// each additional pool successively doubling.  Optionally specify a
    /// `growthStrategy` indicating whether the number of blocks allocated
    /// at once for every internally created `bdlma::Pool` should be either
    /// fixed or grow geometrically, starting with 1.  If `growthStrategy`
    /// is not specified, optionally specify a `growthStrategyArray`,
    /// indicating the strategies for each individual `bdlma::Pool` created
    /// by this object.  If neither `growthStrategy` nor
    /// `growthStrategyArray` is specified, the allocation strategy for each
    /// internally created `bdlma::Pool` object will grow geometrically,
    /// starting from 1.  Optionally specify a `maxBlocksPerChunk`,
    /// indicating the maximum number of blocks to be allocated at once when
    /// a pool must be replenished.  If `maxBlocksPerChunk` is not
    /// specified, optionally specify a `maxBlocksPerChunkArray`, indicating
    /// the maximum number of blocks to allocate at once for each
    /// individually created `bdlma::Pool` object.  If neither
    /// `maxBlocksPerChunk` nor `maxBlocksPerChunkArray` is specified, an
    /// implementation-defined value is used.  Optionally specify a
    /// `basicAllocator` used to supply memory.  If `basicAllocator` is 0,
    /// the currently installed default allocator is used.  Memory
    /// allocation (and deallocation) requests will be satisfied using the
    /// internally maintained pool managing memory blocks of the smallest
    /// size not less than the requested size, or directly from the
    /// underlying allocator (supplied at construction), if no internal pool
    /// managing memory blocks of sufficient size exists.  The behavior is
    /// undefined unless `1 <= numPools`, `growthStrategyArray` has at least
    /// `numPools` strategies, `1 <= maxBlocksPerChunk`, and
    /// `maxBlocksPerChunkArray` has at least `numPools` positive values.
    /// Note that, on platforms where
    /// `8 < bsls::AlignmentUtil::BSLS_MAX_ALIGNMENT`, excess memory may be
    /// allocated for pools managing smaller blocks.  Also note that the
    /// maximum need not be an integral power of 2; if geometric growth
    /// would exceed a maximum value, the chunk size is capped at that
    /// value.
    MultipoolAllocator(int                                numPools,
                       const bsls::BlockGrowth::Strategy *growthStrategyArray,
                       bslma::Allocator                  *basicAllocator = 0);
    MultipoolAllocator(int                                numPools,
                       const bsls::BlockGrowth::Strategy *growthStrategyArray,
                       int                                maxBlocksPerChunk,
                       bslma::Allocator                  *basicAllocator = 0);
    MultipoolAllocator(int                          numPools,
                       bsls::BlockGrowth::Strategy  growthStrategy,
                       const int                   *maxBlocksPerChunkArray,
                       bslma::Allocator            *basicAllocator = 0);
    MultipoolAllocator(
                     int                                numPools,
                     const bsls::BlockGrowth::Strategy *growthStrategyArray,
                     const int                         *maxBlocksPerChunkArray,
                     bslma::Allocator                  *basicAllocator = 0);

    /// Destroy this multipool allocator.  All memory allocated from this
    /// allocator is released.
    ~MultipoolAllocator() BSLS_KEYWORD_OVERRIDE;

    // MANIPULATORS

    /// Reserve memory from this multipool allocator to satisfy memory
    /// requests for at least the specified `numObjects` having the
    /// specified `size` (in bytes) before the pool replenishes.  If `size`
    /// is 0, this method has no effect.  The behavior is undefined unless
    /// `size <= maxPooledBlockSize()` and `0 <= numObjects`.
    void reserveCapacity(bsls::Types::size_type size, int numObjects);

                                // Virtual Functions

    /// Return the address of a contiguous block of maximally-aligned memory
    /// of (at least) the specified `size` (in bytes).  If `size` is 0, no
    /// memory is allocated and 0 is returned.  If
    /// `size > maxPooledBlockSize()`, the memory allocation is managed
    /// directly by the underlying allocator, but will not be pooled .
    void *allocate(bsls::Types::size_type size) BSLS_KEYWORD_OVERRIDE;

    /// Return the memory block at the specified `address` back to this
    /// allocator for reuse.  If `address` is 0, this method has no effect.
    /// The behavior is undefined unless `address` was allocated by this
    /// allocator, and has not already been deallocated.
    void deallocate(void *address) BSLS_KEYWORD_OVERRIDE;

    /// Release all memory currently allocated through this multipool
    /// allocator.
    void release() BSLS_KEYWORD_OVERRIDE;

    // ACCESSORS

    /// Return the number of pools managed by this multipool allocator.
    int numPools() const;

    /// Return the maximum size of memory blocks that are pooled by this
    /// multipool allocator.  Note that the maximum value is defined as:
    /// ```
    /// 2 ^ (numPools + 2)
    /// ```
    /// where `numPools` is either specified at construction, or an
    /// implementation-defined value.
    bsls::Types::size_type maxPooledBlockSize() const;
};

// ============================================================================
//                             INLINE DEFINITIONS
// ============================================================================

                         // ------------------------
                         // class MultipoolAllocator
                         // ------------------------

// CREATORS
inline
MultipoolAllocator::MultipoolAllocator(
                     bslma::Allocator                  *basicAllocator)
: d_multipool(basicAllocator)
{
}

inline
MultipoolAllocator::MultipoolAllocator(
                     int                                numPools,
                     bslma::Allocator                  *basicAllocator)
: d_multipool(numPools, basicAllocator)
{
}

inline
MultipoolAllocator::MultipoolAllocator(
                     bsls::BlockGrowth::Strategy        growthStrategy,
                     bslma::Allocator                  *basicAllocator)
: d_multipool(growthStrategy, basicAllocator)
{
}

inline
MultipoolAllocator::MultipoolAllocator(
                     int                                numPools,
                     bsls::BlockGrowth::Strategy        growthStrategy,
                     bslma::Allocator                  *basicAllocator)
: d_multipool(numPools, growthStrategy, basicAllocator)
{
}

inline
MultipoolAllocator::MultipoolAllocator(
                     int                                numPools,
                     const bsls::BlockGrowth::Strategy *growthStrategyArray,
                     bslma::Allocator                  *basicAllocator)
: d_multipool(numPools, growthStrategyArray, basicAllocator)
{
}

inline
MultipoolAllocator::MultipoolAllocator(
                     int                                numPools,
                     bsls::BlockGrowth::Strategy        growthStrategy,
                     int                                maxBlocksPerChunk,
                     bslma::Allocator                  *basicAllocator)
: d_multipool(numPools, growthStrategy, maxBlocksPerChunk, basicAllocator)
{
}

inline
MultipoolAllocator::MultipoolAllocator(
                     int                                numPools,
                     const bsls::BlockGrowth::Strategy *growthStrategyArray,
                     int                                maxBlocksPerChunk,
                     bslma::Allocator                  *basicAllocator)
: d_multipool(numPools, growthStrategyArray, maxBlocksPerChunk, basicAllocator)
{
}

inline
MultipoolAllocator::MultipoolAllocator(
                     int                                numPools,
                     bsls::BlockGrowth::Strategy        growthStrategy,
                     const int                         *maxBlocksPerChunkArray,
                     bslma::Allocator                  *basicAllocator)
: d_multipool(numPools, growthStrategy, maxBlocksPerChunkArray, basicAllocator)
{
}

inline
MultipoolAllocator::MultipoolAllocator(
                     int                                numPools,
                     const bsls::BlockGrowth::Strategy *growthStrategyArray,
                     const int                         *maxBlocksPerChunkArray,
                     bslma::Allocator                  *basicAllocator)
: d_multipool(numPools,
              growthStrategyArray,
              maxBlocksPerChunkArray,
              basicAllocator)
{
}

// MANIPULATORS
inline
void *MultipoolAllocator::allocate(bsls::Types::size_type size)
{
    return d_multipool.allocate(size);
}

inline
void MultipoolAllocator::deallocate(void *address)
{
    if (BSLS_PERFORMANCEHINT_PREDICT_LIKELY(address != 0)) {
        d_multipool.deallocate(address);
    }
}

inline
void MultipoolAllocator::release()
{
    d_multipool.release();
}

inline
void MultipoolAllocator::reserveCapacity(bsls::Types::size_type size,
                                         int                    numObjects)
{
    d_multipool.reserveCapacity(size, numObjects);
}

// ACCESSORS
inline
int MultipoolAllocator::numPools() const
{
    return d_multipool.numPools();
}

inline
bsls::Types::size_type MultipoolAllocator::maxPooledBlockSize() const
{
    return d_multipool.maxPooledBlockSize();
}

}  // close package namespace
}  // close enterprise namespace

#endif

// ----------------------------------------------------------------------------
// Copyright 2016 Bloomberg Finance L.P.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ----------------------------- END-OF-FILE ----------------------------------
