Outline
Purpose
Provide a proctor for destroying arrays.
Classes
- See also
- bslma_autodestructor, 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;
public:
: 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 = '?';
}
: 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,
: 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_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_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; }
{
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 {
}
}
}
Definition bslma_allocator.h:457
virtual void deallocate(void *address)=0
virtual void * allocate(size_type size)=0
bool operator==(const FileCleanerConfiguration &lhs, const FileCleanerConfiguration &rhs)
bsl::ostream & print(bsl::ostream &stream, const TYPE &object, int level=0, int spacesPerLevel=4)
Definition bdlb_printmethods.h:719
Definition balxml_encoderoptions.h:68
Definition bdlbb_blob.h:576
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,
{
TestType *finish = divider + (divider - start);
std::memcpy((void *)divider,
start,
(divider - start) * sizeof(TestType));
Obj guard(start, divider, divider, finish, allocator);
while (guard.middle() < guard.end()) {
new (guard.destination()) TestType(value, allocator);
guard.advance();
}
}
Definition bslalg_autoarraymovedestructor.h:418
#define BSLMF_ASSERT(expr)
Definition bslmf_assert.h:229
Definition bslma_usesbslmaallocator.h:343
Definition bslmf_isbitwisemoveable.h:718
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:
TestType *array = (TestType *) ta.
allocate(10 *
sizeof(TestType));
Definition bslma_testallocator.h:384
void * allocate(size_type size) BSLS_KEYWORD_OVERRIDE
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:
bsls::Types::Int64 numBlocksInUse() const
Definition bslma_testallocator.h:1087
long long Int64
Definition bsls_types.h:132
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:
#define BSLMA_TESTALLOCATOR_EXCEPTION_TEST_BEGIN(BSLMA_TESTALLOCATOR)
Definition bslma_testallocator.h:912
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.
#define BSLMA_TESTALLOCATOR_EXCEPTION_TEST_END
Definition bslma_testallocator.h:955
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('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: for (int i = 0; i < 10; ++i) {
array[i].~TestType();
}
void deallocate(void *address) BSLS_KEYWORD_OVERRIDE
bsls::Types::Int64 numBytesInUse() const
Definition bslma_testallocator.h:1111
◆ bslalg_AutoArrayMoveDestructor