Quick Links:

bal | bbl | bdl | bsl

Namespaces

Component bslalg_autoarraymovedestructor
[Package bslalg]

Provide a proctor for destroying arrays. More...

Namespaces

namespace  bslalg

Detailed Description

Outline
Purpose:
Provide a proctor for destroying arrays.
Classes:
bslalg::AutoArrayMoveDestructor exception-neutrality guard for arrays
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
                             // ==============

  class TestType {
      // This test type contains a 'char' in some allocated storage.  It
      // counts the number of default and copy constructions, assignments,
      // and destructions.  It has no traits other than using a 'bslma'
      // allocator.  It could have the bit-wise moveable traits but we defer
      // that trait to the 'MoveableTestType'.

      char             *d_data_p;
      bslma::Allocator *d_allocator_p;

    public:
      // CREATORS
      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;
      }

      // MANIPULATORS
      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; }

      // ACCESSORS
      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");
          }
      }
  };

  // FREE OPERATORS
  bool operator==(const TestType& lhs, const TestType& rhs)
  {
      return lhs.datum() == rhs.datum();
  }

  // TRAITS
  namespace BloombergLP {

  namespace bslma {
  template <> struct UsesBslmaAllocator<TestType> : bsl::true_type {};
  }  // close namespace bslma

  namespace bslmf {
  template <> struct IsBitwiseMoveable<TestType> : bsl::true_type {};
  }  // close namespace bslmf

  }  // close enterprise namespace
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)
      // The memory in the specified range '[ start, divider )' contains
      // valid elements, and the range of valid elements is to be doubled by
      // inserting 'divider - start' copies of the specified 'value' at
      // 'start', shifting the existing valid values back in memory.  Assume
      // that following the pointer 'divider' is sufficient uninitialized
      // memory, and that the type 'TestType' is bitwise-movable
      // ('AutoArrayMoveDestructor' will only work bitwise-movable types).
  {
      TestType *finish = divider + (divider - start);

      BSLMF_ASSERT(bslmf::IsBitwiseMoveable< TestType>::value);
      BSLMF_ASSERT(bslma::UsesBslmaAllocator<TestType>::value);

      // The range '[ start, divider )' contains valid elements.  The range
      // '[ divider, finish )' is of equal length and contains uninitialized
      // memory.  We want to insert 'divider - start' copies of the specified
      // 'value' at the front half of the range '[ start, finish )', moving
      // the existing elements back to make room for them.  Note that the
      // copy constructor of 'TestType' allocates memory and may throw, so we
      // have to leave the array in a somewhat predictable state if we do
      // throw.  What the bslalg::AutoArrayMoveDestructor will do is
      // guarantee that, if it is destroyed before the insertion is complete,
      // the range '[ start, divider )' will contain valid elements, and that
      // no other valid elements will exist.
      //
      // Note that the existing elements, which are bitwise-moveable, may be
      // *moved* about the container without the possibility of throwing an
      // exception, but the newly inserted elements must be copy-constructed
      // (requiring memory allocation).
      //
      // First, move the valid elements from '[ start, divider )' to
      // '[ divider, finish )'.  This can be done without risk of a throw
      // occurring.

      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()) {
          // Call the copy constructor, which may throw.

          new (guard.destination()) TestType(value, allocator);

          // 'guard.advance()' increments 'guard.destination()' and
          // 'guard.middle()' by one.

          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.
  TestType value('v');
Then, we create a test allocator, and use it to allocate memory for an array of TestType objects:
  bslma::TestAllocator ta;

  TestType *array = (TestType *) ta.allocate(10 * sizeof(TestType));
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.
      assert(ta.numBlocksInUse() == N);
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:
  1. we have allocated exactly 5 more blocks of memory, since each TestType object allocates one block and insertItems created 5 more TestType objects.
  2. 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:
  for (int i = 0; i < 10; ++i) {
      array[i].~TestType();
  }
  ta.deallocate(array);

  assert(0 == ta.numBytesInUse());