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

Detailed Description

Outline

Purpose

Provide a memory allocator that counts allocated bytes.

Classes

See also
bslma_allocator, bslma_testallocator

Description

This component provides a special-purpose counting allocator, bdlma::CountingAllocator, that implements the bslma::Allocator protocol and provides instrumentation to track: (1) the number of bytes currently in use (numBytesInUse), and (2) the cumulative number of bytes that have ever been allocated (numBytesTotal). The accumulated statistics are based solely on the number of bytes requested in calls to the allocate method. A print method is provided to output the current state of the allocator's byte counts to a specified bsl::ostream:

,------------------------.
`------------------------'
| ctor/dtor
| numBytesInUse
| numBytesTotal
| name
| print
V
,----------------.
( bslma::Allocator )
`----------------'
allocate
deallocate
Definition bdlma_countingallocator.h:346

Like many other allocators, bdlma::CountingAllocator relies on the currently installed default allocator (see bslma_default ) at construction. Clients may, however, override this allocator by supplying (at construction) any other allocator implementing the bslma::Allocator protocol provided that it is fully thread-safe.

Note that a bdlma::CountingAllocator necessarily incurs some overhead in order to provide its byte-counting functionality. However, this overhead is substantially less than that incurred by the bslma::TestAllocator (see bslma_testallocator ), which keeps track of the same two statistics that are maintained by a bdlma::CountingAllocator. Consequently, use of a bdlma::CountingAllocator may be appropriate in cases where the overhead of bslma::TestAllocator is too onerous. In particular, a counting allocator may be suitable even for production use in certain situations, whereas the test allocator is not intended for production use under any circumstance.

Byte Counts

The two byte counts maintained by bdlma::CountingAllocator are initialized to 0 at construction and increased with each call to allocate by size, i.e., by the actual number of bytes requested. Each call to deallocate decreases the numBytesInUse count by the same amount by which the byte count was increased in the original allocate call. The number of bytes currently in use is returned by numBytesInUse and the total number of bytes ever allocated is returned by numBytesTotal.

Thread Safety

The bdlma::CountingAllocator class is fully thread-safe (see bsldoc_glossary ) provided that the underlying allocator (established at construction) is fully thread-safe.

Usage

This section illustrates intended use of this component.

Example 1: Tracking a Container's Dynamic Memory Use

In this example, we demonstrate how a counting allocator may be used to track the amount of dynamic memory used by a container. The container used for illustration is DoubleStack, a stack of out-of-place double values.

First, we show the interface of the DoubleStack class:

// doublestack.h
class DoubleStack {
// This class implements a stack of out-of-place 'double' values.
// DATA
double **d_stack_p; // dynamically allocated array of
// 'd_capacity' elements
int d_capacity; // physical capacity of the stack
// (in elements)
int d_length; // logical index of next available
// stack element
bslma::Allocator *d_allocator_p; // memory allocator (held, not
// owned)
// NOT IMPLEMENTED
DoubleStack(const DoubleStack&);
DoubleStack& operator=(const DoubleStack&);
private:
// PRIVATE MANIPULATORS
void increaseCapacity();
// Increase the capacity of this stack by at least one element.
public:
// CREATORS
explicit
DoubleStack(bslma::Allocator *basicAllocator = 0);
// Create a stack for 'double' values having an initial capacity to
// hold one element. Optionally specify a 'basicAllocator' used to
// supply memory. If 'basicAllocator' is 0, the currently
// installed default allocator is used.
~DoubleStack();
// Delete this object.
// MANIPULATORS
void push(double value);
// Add the specified 'value' to the top of this stack.
void pop();
// Remove the element at the top of this stack. The behavior is
// undefined unless this stack is non-empty.
// ACCESSORS
// ...
};
Definition bslma_allocator.h:457

Next, we show the (elided) implementation of DoubleStack.

The default constructor creates a stack having the capacity for one element (the implementation of the destructor is not shown):

// doublestack.cpp
// ...
// TYPES
enum { k_INITIAL_CAPACITY = 1, k_GROWTH_FACTOR = 2 };
// CREATORS
DoubleStack::DoubleStack(bslma::Allocator *basicAllocator)
: d_stack_p(0)
, d_capacity(k_INITIAL_CAPACITY)
, d_length(0)
, d_allocator_p(bslma::Default::allocator(basicAllocator))
{
d_stack_p = (double **)
d_allocator_p->allocate(d_capacity * sizeof *d_stack_p);
}
Definition balxml_encoderoptions.h:68

The push method first ensures that the array has sufficient capacity to accommodate an additional value, then allocates a block in which to store that value:

// MANIPULATORS
void DoubleStack::push(double value)
{
if (d_length >= d_capacity) {
increaseCapacity();
}
double *stackValue = (double *)d_allocator_p->allocate(sizeof(double));
*stackValue = value;
d_stack_p[d_length] = stackValue;
++d_length;
}

The pop method asserts that the stack is not empty before deallocating the block used to store the element at the top of the stack:

void DoubleStack::pop()
{
BSLS_ASSERT(0 < d_length);
d_allocator_p->deallocate(d_stack_p[d_length - 1]);
--d_length;
}
#define BSLS_ASSERT(X)
Definition bsls_assert.h:1804

The push method (above) made use of the private increaseCapacity method, which, in turn, makes use of the reallocate helper function (static to the .cpp file). Note that increaseCapacity (below) increases the capacity of the double * array by a factor of 2 each time that it is called:

// HELPER FUNCTIONS
static
void reallocate(double ***array,
int newCapacity,
int length,
bslma::Allocator *allocator)
// Reallocate memory in the specified 'array' to accommodate the
// specified 'newCapacity' elements using the specified 'allocator'.
// The specified 'length' number of leading elements are preserved.
// The behavior is undefined unless 'newCapacity > length'.
{
BSLS_ASSERT(newCapacity > length);
double **tmp = *array;
*array = (double **)allocator->allocate(newCapacity * sizeof **array);
bsl::memcpy(*array, tmp, length * sizeof **array); // commit
allocator->deallocate(tmp);
}
// PRIVATE MANIPULATORS
void DoubleStack::increaseCapacity()
{
const int newCapacity = d_capacity * k_GROWTH_FACTOR;
// reallocate can throw
reallocate(&d_stack_p, newCapacity, d_length, d_allocator_p);
d_capacity = newCapacity; // commit
}
virtual void deallocate(void *address)=0
virtual void * allocate(size_type size)=0

Now, we are ready to employ a CountingAllocator to illustrate the dynamic memory use of DoubleStack. We first define two constants that facilitate portability of this example across 32- and 64-bit platforms:

const int DBLSZ = sizeof(double);
const int PTRSZ = sizeof(double *);

First, we define a CountingAllocator, ca. At construction, a counting allocator can be configured with an optional name and an optional allocator. In this case, we give ca a name to distinguish it from other counting allocators, but settle for using the default allocator:

bdlma::CountingAllocator ca("'DoubleStack' Allocator");

Next, we create a DoubleStack, supplying it with ca, and assert the expected memory use incurred by the default constructor:

DoubleStack stack(&ca);
assert(1 * PTRSZ == ca.numBytesInUse());
assert(1 * PTRSZ == ca.numBytesTotal());

Next, we push an element onto the stack. The first push incurs an additional allocation to store (out-of-place) the value being inserted:

stack.push(1.54); assert(1 * PTRSZ + 1 * DBLSZ == ca.numBytesInUse());
assert(1 * PTRSZ + 1 * DBLSZ == ca.numBytesTotal());

Next, we push a second element onto the stack. In this case, two allocations result, one due to the resizing of the internal array and one required to store the new value out-of-place:

stack.push(0.99); assert(2 * PTRSZ + 2 * DBLSZ == ca.numBytesInUse());
assert(3 * PTRSZ + 2 * DBLSZ == ca.numBytesTotal());

Next, we pop the top-most element from the stack. The number of bytes in use decreases by the amount used to store the popped element out-of-place:

stack.pop(); assert(2 * PTRSZ + 1 * DBLSZ == ca.numBytesInUse());
assert(3 * PTRSZ + 2 * DBLSZ == ca.numBytesTotal());

Finally, we print the state of ca to standard output:

ca.print(bsl::cout);

which displays the following on a 32-bit platform:

----------------------------------------
Counting Allocator State
----------------------------------------
Allocator name: 'DoubleStack' Allocator
Bytes in use: 16
Bytes in total: 28