Provide a mechanism to summarize bslma::TestAllocator
object use.
More...
Namespaces |
namespace | bslma |
Detailed Description
- Outline
-
-
- Purpose:
- Provide a mechanism to summarize
bslma::TestAllocator
object use.
-
- Classes:
-
- See also:
- Component 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 {
size_t d_capacity;
char *d_description_p;
bslma::Allocator *d_allocator_p;
public:
explicit MyClass(bslma::Allocator *basicAllocator = 0);
~MyClass();
void setDescription(const char *value);
const char *description() const;
};
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);
}
inline
void MyClass::setDescription(const char *value)
{
BSLS_ASSERT_SAFE(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);
}
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).
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: 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: 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: 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. 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;
BSLMA_TESTALLOCATOR_EXCEPTION_TEST_BEGIN(oa) {
if (veryVeryVerbose) { T_ T_ Q(ExceptionTestBody) }
obj.setDescription(DESCRIPTION1);
assert(oam.isTotalUp());
assert(oam.isInUseUp());
assert(oam.isMaxUp());
} BSLMA_TESTALLOCATOR_EXCEPTION_TEST_END
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);
assert(0 == strcmp(DESCRIPTION1, obj.description()));
assert(oam2.isTotalSame());
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());
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());
assert(oam2.isInUseSame());
assert(oam2.isMaxUp());
bsls::Types::Int64 maxAfterSet = oa.numBlocksMax();
assert(1 == maxAfterSet - maxBeforeSet);
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
. bslma::TestAllocatorMonitor oam3(&oa);
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());
assert(oam3.isInUseSame());
assert(oam3.isMaxSame());
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());