Provide a proctor for destroying arrays.
More...
Detailed Description
- Outline
-
-
- Purpose:
- Provide a proctor for destroying arrays.
-
- Classes:
-
- See also:
- Component bslma_autodestructor, Component bslalg_autoarraydestructor
-
- Description:
- This component provides a proctor object to manage a contiguous (in-place) sequence of otherwise-unmanaged instances of a user-defined type. If not explicitly released, all objects managed by the proctor object are automatically destroyed by the proctor's destructor or moved back to their original area, using the
bslalg_arraydestructionprimitives
and std::memmove
. This component is intended to be used only with bit-wise moveable types, and for a very special purpose as shown in the usage example.
- Overview of the operation of
AutoArrayMoveDestructor
: ---------------------------------------------------------------------------- Suppose we want to double the length of an array by prepending copies a value
at the start of the array. Note that we assume there is ample uninitialized memory after the end of the initial array for these new values to be inserted.
- Legend:
'ABCDE' -- initial array elements.
'v' -- copy of specified 'value' being inserted.
'.' -- (period) uninitialized memory.
'^(,)' -- area guarded by 'AutoArrayMoveDestructor', where:
'^' -- position of 'guard.destination()'
'(' -- position of 'guard.begin()'
',' -- (comma) position of 'guard.middle()'
')' -- position of 'guard.end()'
The copy constructor for the type being inserted may throw, so we need to have a guard object that allows us to make some guarantee about the state of the array after the guard is destroyed. What we want to guarantee is that there are as many valid objects at the start of the array as before with no other valid objects in existence.
- The following steps show a successful operation prepending copies of the value
v
: 1: 'ABCDE.....' -- initial memory.
2: '.....ABCDE' -- memory after first 'std::memcpy'.
3: '^.....(,ABCDE)' -- memory immediately after 'guard' is set
4: 'vv^...(AB,CDE)' -- memory after 2 copies of 'value' have been
created, and 'guard.advance()' has been called
twice.
5: 'vvvvv^(ABCDE,)' -- memory after insertion is completed
6: 'vvvvvABCDE' -- memory after guard was destroyed (at which point
'guard.middle() == guard.end()' so destructor did
nothing.
Now suppose we threw after step 4, destroying guard
. 4: 'vv^...(AB,CDE)' -- same as step '4' above, before destructor starts
5b: 'vv^CDE(AB,...)' -- memory after 'guard's destructor moves 'CDE' back
to their position before we began
6b: 'vv^CDE(..,...)' -- memory after 'guard's destructor destroys 'A' and
'B'
7b: 'vvCDE.....' -- memory after 'guard's destructor completes
We now have 5 valid elements in the beginning of the range, as it was when we started, making the situation predictable for our next destructor.
- This was a very simple case, but using this guard in conjunction with
bslalg::AutoArrayDestructor
, we can implement the more general case of inserting arbitrary numbers of elements at the beginning of an array.
-
- Usage:
- In this section we show intended use of this component.
-
- Example 1: Doubling the Length of an Array:
- First, we create the class
TestType
, which is bitwise-movable and allocates memory upon construction:
class TestType {
char *d_data_p;
bslma::Allocator *d_allocator_p;
public:
explicit TestType(bslma::Allocator *basicAllocator = 0)
: d_data_p(0)
, d_allocator_p(bslma::Default::allocator(basicAllocator))
{
++numDefaultCtorCalls;
d_data_p = (char *)d_allocator_p->allocate(sizeof(char));
*d_data_p = '?';
}
explicit TestType(char c, bslma::Allocator *basicAllocator = 0)
: d_data_p(0)
, d_allocator_p(bslma::Default::allocator(basicAllocator))
{
++numCharCtorCalls;
d_data_p = (char *)d_allocator_p->allocate(sizeof(char));
*d_data_p = c;
}
TestType(const TestType& original,
bslma::Allocator *basicAllocator = 0)
: d_data_p(0)
, d_allocator_p(bslma::Default::allocator(basicAllocator))
{
++numCopyCtorCalls;
if (&original != this) {
d_data_p = (char *)d_allocator_p->allocate(sizeof(char));
*d_data_p = *original.d_data_p;
}
}
~TestType()
{
++numDestructorCalls;
*d_data_p = '_';
d_allocator_p->deallocate(d_data_p);
d_data_p = 0;
}
TestType& operator=(const TestType& rhs)
{
++numAssignmentCalls;
if (&rhs != this) {
char *newData = (char *)d_allocator_p->allocate(sizeof(char));
*d_data_p = '_';
d_allocator_p->deallocate(d_data_p);
d_data_p = newData;
*d_data_p = *rhs.d_data_p;
}
return *this;
}
void setDatum(char c) { *d_data_p = c; }
char datum() const { return *d_data_p; }
void print() const
{
if (d_data_p) {
assert(isalpha(*d_data_p));
printf("%c (int: %d)\n", *d_data_p, (int)*d_data_p);
} else {
printf("VOID\n");
}
}
};
bool operator==(const TestType& lhs, const TestType& rhs)
{
return lhs.datum() == rhs.datum();
}
namespace BloombergLP {
namespace bslma {
template <> struct UsesBslmaAllocator<TestType> : bsl::true_type {};
}
namespace bslmf {
template <> struct IsBitwiseMoveable<TestType> : bsl::true_type {};
}
}
Then, we define the function insertItems
which uses AutoArrayMoveDestructor
to ensure that if an exception is thrown (e.g., when allocating memory), the array will be left in a state where it has the same number of elements, in the same location, as when the function begin (though not necessarily the same value). void insertItems(TestType *start,
TestType *divider,
const TestType value,
bslma::Allocator *allocator)
{
TestType *finish = divider + (divider - start);
BSLMF_ASSERT(bslmf::IsBitwiseMoveable< TestType>::value);
BSLMF_ASSERT(bslma::UsesBslmaAllocator<TestType>::value);
std::memcpy((void *)divider,
start,
(divider - start) * sizeof(TestType));
typedef bslalg::AutoArrayMoveDestructor<TestType> Obj;
Obj guard(start, divider, divider, finish, allocator);
while (guard.middle() < guard.end()) {
new (guard.destination()) TestType(value, allocator);
guard.advance();
}
}
Next, within the main
function of our task, we create our value
object, whose value will be v
, to be inserted into the front of our range. Then, we create a test allocator, and use it to allocate memory for an array of TestType
objects: Next, we construct the first 5 elements of the array to have the values ABCDE
. TestType *p = array;
new (p++) TestType('A', &ta);
new (p++) TestType('B', &ta);
new (p++) TestType('C', &ta);
new (p++) TestType('D', &ta);
new (p++) TestType('E', &ta);
Then, we record the number of outstanding blocks in the allocator: Next, we enter an exception test
block, which will repeatedly enter a block of code, catching exceptions throw by the test allocator ta
on each iteration: Then, we observe that even if we've just caught an exception and re-entered the block, the amount of memory outstanding is unchanged from before we entered the block. Note that when we threw, some of the values of the 5 elements of the array may have been changed to v
, otherwise they will be unchanged.
- Next, we re-initialize those elements that have been overwritten in the last pass with
value
to their values before we entered the block: if ('v' == array[0].datum()) array[0].setDatum('A');
if ('v' == array[1].datum()) array[1].setDatum('B');
if ('v' == array[2].datum()) array[2].setDatum('C');
if ('v' == array[3].datum()) array[3].setDatum('D');
if ('v' == array[4].datum()) array[4].setDatum('E');
Then, we call insertItems
, which may throw: insertItems(array, p, value, &ta);
Next, we exit the except testing block. Now, we verify that:
-
we have allocated exactly 5 more blocks of memory, since each
TestType
object allocates one block and insertItems
created 5 more TestType
objects.
-
the values of the elements of the array are as expected.
assert(ta.numBlocksInUse() == N + 5);
assert('v' == array[0].datum());
assert('v' == array[1].datum());
assert('v' == array[2].datum());
assert('v' == array[3].datum());
assert('v' == array[4].datum());
assert('A' == array[5].datum());
assert('B' == array[6].datum());
assert('C' == array[7].datum());
assert('D' == array[8].datum());
assert('E' == array[9].datum());
Finally, we destroy our array and check the allocator to verify no memory was leaked: