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

Typedefs

typedef bslma::TestAllocatorMonitor bslma_TestAllocatorMonitor
 This alias is defined for backward compatibility.
 

Detailed Description

Outline

Purpose

Provide a mechanism to summarize bslma::TestAllocator object use.

Classes

See also
bslma_testallocator

Description

This component provides a single mechanism class, bslma::TestAllocatorMonitor, which is used, in concert with bslma::TestAllocator, in the implementation of test drivers. The bslma::TestAllocatorMonitor class provides boolean accessors indicating whether associated test allocator state has changed (or not) since construction of the monitor. Using bslma::TestAllocatorMonitor objects often result in test cases that are more concise, easier to read, and less error prone than test cases that directly access the test allocator for state information.

Statistics

The test allocator statistics tracked by the test allocator monitor along with the boolean accessors used to observe a change in those statistics are shown in the table below. The change (or lack of change) reported by these accessors are relative to the value of the test allocator statistic at the construction of the monitor. Note that each of these statistics count blocks of memory (i.e., number of allocations from the allocator), and do not depend on the number of bytes in those allocated blocks.

Statistic Is-Same Method Is-Up Method Is-Down Method
-------------- -------------- ------------ --------------
numBlocksInUse isInUseSame isInUseUp isInUseDown
numBlocksMax isMaxSame isMaxUp none
numBlocksTotal isTotalSame isTotalUp none

The numBlocksMax and numBlocksTotal statistics have values that are monotonically non-decreasing; hence, they need no "Is-Down" methods. Note that if a monitor is created for an allocator with outstanding blocks ("in use"), then it is possible for the allocator's count of outstanding blocks to drop below the value seen by the monitor at construction.

Usage

This section illustrates intended use of this component.

Example 1: Standard Usage

Classes taking bslma::allocator objects have many requirements (and thus, many testing concerns) that other classes do not. Here we illustrate how bslma::TestAllocatorMonitor objects (in conjunction with bslma::TestAllocator objects) can be used in a test driver to succinctly address many concerns of an object's use of allocators.

First, for a test subject, we introduce MyClass, an unconstrained attribute class having a single, null-terminated ascii string attribute, description. For the sake of brevity, MyClass defines only a default constructor, a primary manipulator (the setDescription method), and a basic accessor (the description method). These suffice for the purposes of these example. Note that a proper attribute class would also implement value and copy constructors, operator==, an accessor for the allocator, and other methods.

class MyClass {
// This unconstrained (value-semantic) attribute class has a single,
// null-terminated ascii string attribute, 'description'.
// DATA
size_t d_capacity; // available memory
char *d_description_p; // string data
bslma::Allocator *d_allocator_p; // held, not owned
public:
// CREATORS
explicit MyClass(bslma::Allocator *basicAllocator = 0);
// Create a 'MyClass' object having the (default) attribute values:
//..
// description() == ""
//..
// Optionally specify a 'basicAllocator' used to supply memory. If
// 'basicAllocator' is 0, the currently installed default allocator
// is used.
~MyClass();
// Destroy this object.
// MANIPULATORS
void setDescription(const char *value);
// Set the null-terminated ascii string 'description' attribute of
// this object to the specified 'value'. On completion, the
// 'description' method returns the address of a copy of the ascii
// string at 'value'.
// ACCESSORS
const char *description() const;
// Return the value of the null-terminated ascii string
// 'description' attribute of this object.
};
// ========================================================================
// INLINE FUNCTION DEFINITIONS
// ========================================================================
// -------------
// class MyClass
// -------------
// CREATORS
inline
MyClass::MyClass(bslma::Allocator *basicAllocator)
: d_capacity(0)
, d_description_p(0)
, d_allocator_p(bslma::Default::allocator(basicAllocator))
{
}
inline
MyClass::~MyClass()
{
BSLS_ASSERT_SAFE(0 <= d_capacity);
d_allocator_p->deallocate(d_description_p);
}
// MANIPULATORS
inline
void MyClass::setDescription(const char *value)
{
size_t size = std::strlen(value) + 1;
if (size > d_capacity) {
char *newMemory = (char *) d_allocator_p->allocate(size);
d_allocator_p->deallocate(d_description_p);
d_description_p = newMemory;
d_capacity = size;
}
std::memcpy(d_description_p, value, size);
}
Definition bslma_allocator.h:457
#define BSLS_ASSERT_SAFE(X)
Definition bsls_assert.h:1762
bsl::size_t size(const TYPE &array)
Return the number of elements in the specified array.
Definition balxml_encoderoptions.h:68

Notice that the implementation of the manipulator allocates/deallocates memory before updating the object. This ordering leaves the object unchanged in case the allocator throws an exception (part of the strong exception guarantee). This is an implementation detail, not a part of the contract (in this example).

// ACCESSORS
inline
const char *MyClass::description() const
{
return d_description_p ? d_description_p : "";
}

Then, we design a test-driver for MyClass. Our allocator-related concerns for MyClass include:

Concerns:
//: 1 Any memory allocation is from the object allocator.
//:
//: 2 Every object releases any allocated memory at destruction.
//:
//: 3 No accessor allocates any memory.
//:
//: 4 All memory allocation is exception-neutral.
//:
//: 5 QoI: The default constructor allocates no memory.
//:
//: 6 QoI: When possible, memory is cached for reuse.

Notice that some of these concerns (e.g., C-5..6) are not part of the class's documented, contractual behavior. These are classified as Quality of Implementation (QoI) concerns.

Next, we define a test plan. For example, a plan to test these concerns is:

Plan:
//: 1 Setup global and default allocators:
//:
//: 1 Create two 'bslma::TestAllocator' objects and, for each of these,
//: create an associated 'bslma::TestAllocatorMonitor' object.
//:
//: 2 Install the two allocators as the global and default allocators.
//:
//: 2 Confirm that default construction allocates no memory: (C-5)
//:
//: 1 Construct a 'bslma::TestAllocatorMonitor' object to be used passed
//: to test objects on their construction, and an associated
//:
//: 2 In an inner block, default construct an object of 'MyClass' using
//: the designated "object" test allocator.
//:
//: 3 Allow the object to go out of scope (destroyed). Confirm that no
//: memory has been allocated from any of the allocators.
//:
//: 3 Exercise an object of 'MyClass' such that memory should be allocated,
//: and then confirm that the object allocator (only) is used: (C-2..4,6)
//:
//: 1 In another inner block, default construct a new test object using
//: the (as yet unused) object allocator.
//:
//: 2 Force the test object to allocate memory by setting its
//: 'descriptor' attribute to a value whose size exceeds the size of
//: the object itself. Confirm that the attribute was set and that
//: memory was allocated.
//:
//: 3 Confirm that the primary manipulator (the 'setDescription' method)
//: is exception-neutral (i.e., exceptions from the allocator are
//: propagated and no memory is leaked). Use the
//: 'BSLMA_TESTALLOCATOR_EXCEPTION_TEST_*' macros to manage the test,
//: and use the test allocator monitor to confirm that memory is
//: allocated on the no-exception code path. (C-4)
//:
//: 4 When the object is holding memory, create an additional test
//: allocator monitor allocator for the object allocator. Use the
//: basic accessor (i.e., the 'description' method) to confirm that the
//: object has the expected value. Check this test allocator monitor
//: to confirm that accessor allocated no memory. (C-3)
//:
//: 5 Change the attribute to a smaller value and confirm that the
//: current memory was reused (i.e., no memory is allocated). (C-6)
//:
//: 6 Destroy the test object by allowing it to go out of scope, and
//: confirm that all allocations are returned. (C-2)
//:
//: 4 Confirm that at no time were the global allocator or the default
//: allocator were used. (C-1)

The implementation of the plan is shown below:

Then, we implement the first portion of the plan. We create the trio of test allocators, their respective test allocator monitors, and install two of the allocators as the global and default allocators:

{
if (verbose) cout << "Setup global and default allocators" << endl;
bslma::TestAllocator ga("global", veryVeryVeryVerbose);
bslma::TestAllocator da("default", veryVeryVeryVerbose);
Definition bslma_testallocatormonitor.h:471
Definition bslma_testallocator.h:384
static Allocator * setGlobalAllocator(Allocator *basicAllocator)
static int setDefaultAllocator(Allocator *basicAllocator)

Then, we default construct a test object using the object allocator, and then, immediately destroy it. The object allocator monitor, oam, shows that the allocator was not used.

if (verbose) cout << "No allocation by Default Constructor " << endl;
bslma::TestAllocator oa("object", veryVeryVeryVerbose);
{
MyClass obj(&oa);
assert(oam.isTotalSame()); // object allocator unused
}

Next, we pass the (still unused) object allocator to another test object. This time, we coerce the object into allocating memory by setting an attribute. (Setting an attribute larger than the receiving object means that the object cannot store the data within its own footprint and must allocate memory.)

if (verbose) cout << "Exercise object" << endl;
{
MyClass obj(&oa);
const char DESCRIPTION1[]="abcdefghijklmnopqrstuvwyz"
"abcdefghijklmnopqrstuvwyz";
assert(sizeof(obj) < sizeof(DESCRIPTION1));
if (veryVerbose) cout << "\tPrimary Manipulator Allocates" << endl;
if (veryVeryVerbose) { T_ T_ Q(ExceptionTestBody) }
obj.setDescription(DESCRIPTION1);
assert(oam.isTotalUp()); // object allocator was used
assert(oam.isInUseUp()); // some outstanding allocation(s)
assert(oam.isMaxUp()); // a maximum was set
#define BSLMA_TESTALLOCATOR_EXCEPTION_TEST_BEGIN(BSLMA_TESTALLOCATOR)
Definition bslma_testallocator.h:912
#define BSLMA_TESTALLOCATOR_EXCEPTION_TEST_END
Definition bslma_testallocator.h:955

Notice, as expected, memory was allocated from object allocator.

Now that the allocator has been used, we create a second monitor to capture the that state. Confirm that the basic accessor (the description method) does not use the allocator.

if (veryVerbose) cout << "\tBasic Accessor does not allocate" << endl;
bslma::TestAllocatorMonitor oam2(&oa); // Captures state of 'oa'
// with outstanding
// allocations.
assert(0 == strcmp(DESCRIPTION1, obj.description()));
assert(oam2.isTotalSame()); // object allocator was not used

Next, confirm that when a shorter value is assigned, the existing memory is reused.

obj.setDescription("a");
assert(0 == std::strcmp("a", obj.description()));
assert(oam2.isTotalSame()); // no allocations

Notice that there are no allocations because the object had sufficient capacity in previously allocated memory to store the short string.

Next, as an additional test, we make the object allocate additional memory by setting a longer attribute: one that exceeds the capacity allocated for DESCRIPTION1. Use the second monitor to confirm that an allocation was performed.

There are tests where using a test allocator monitor does not suffice. Our test object is currently holding memory, if we assign a value that exceeds its current capacity there will be two operations on the object allocator: the allocation of larger memory, and the deallocation of its current memory: in that order, as part of the strong exception guarantee. Thus, the maximum number of allocations should go up by one, and no more.

Note that absence of memory leaks due to exceptions (the other part of the strong exception guarantee is confirmed during the destruction of the object test allocator at the end of this test, which featured exceptions.

bsls::Types::Int64 maxBeforeSet = oa.numBlocksMax();
const char DESCRIPTION2[] = "abcdefghijklmnopqrstuvwyz"
"abcdefghijklmnopqrstuvwyz"
"abcdefghijklmnopqrstuvwyz"
"abcdefghijklmnopqrstuvwyz"
"abcdefghijklmnopqrstuvwyz";
assert(sizeof(DESCRIPTION1) < sizeof(DESCRIPTION2));
obj.setDescription(DESCRIPTION2);
assert(0 == std::strcmp(DESCRIPTION2, obj.description()));
assert(oam2.isTotalUp()); // The object allocator used.
assert(oam2.isInUseSame()); // The outstanding block (allocation)
// count unchanged (even though byte
// outstanding byte count increased).
assert(oam2.isMaxUp()); // Max increased as expected, but was
// did it change only by one? The
// monitor cannot answer that
// question.
bsls::Types::Int64 maxAfterSet = oa.numBlocksMax();
assert(1 == maxAfterSet - maxBeforeSet);
long long Int64
Definition bsls_types.h:132

Notice that our test allocator monitor cannot confirm that the allocator's maximum increased by exactly one. In this case, we must extract our statistics directly from the test allocator.

Note that increment in "max" occurs only the first time through the allocate/deallocate scenario in setDescription.

const char DESCRIPTION3[] = "abcdefghijklmnopqrstuvwyz"
"abcdefghijklmnopqrstuvwyz"
"abcdefghijklmnopqrstuvwyz"
"abcdefghijklmnopqrstuvwyz"
"abcdefghijklmnopqrstuvwyz"
"abcdefghijklmnopqrstuvwyz"
"abcdefghijklmnopqrstuvwyz"
"abcdefghijklmnopqrstuvwyz"
"abcdefghijklmnopqrstuvwyz";
assert(sizeof(DESCRIPTION2) < sizeof(DESCRIPTION3));
obj.setDescription(DESCRIPTION3);
assert(0 == std::strcmp(DESCRIPTION3, obj.description()));
assert(oam3.isTotalUp()); // The object allocator used.
assert(oam3.isInUseSame()); // The outstanding block (allocation)
// count unchanged (even though byte
// outstanding byte count increased).
assert(oam3.isMaxSame()); // A repeat of the scenario for
// 'DESCRIPTION2', so no change in the
// allocator's maximum.

Now, we close scope and check that all object memory was deallocated

}
if (veryVerbose) cout << "\tAll memory returned object allocator"
<< endl;
assert(oam.isInUseSame());

Finally, we check that none of these operations used the default or global allocators.

if (verbose) cout << "Global and Default allocators never used" << endl;
assert(gam.isTotalSame());
assert(dam.isTotalSame());

Typedef Documentation

◆ bslma_TestAllocatorMonitor