// bslalg_autoarraymovedestructor.h -*-C++-*- #ifndef INCLUDED_BSLALG_AUTOARRAYMOVEDESTRUCTOR #define INCLUDED_BSLALG_AUTOARRAYMOVEDESTRUCTOR #include <bsls_ident.h> BSLS_IDENT("$Id: $") //@PURPOSE: Provide a proctor for destroying arrays. // //@CLASSES: // bslalg::AutoArrayMoveDestructor: exception-neutrality guard for arrays // //@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 // // ============== // // 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: //.. // const bsls::Types::Int64 N = ta.numBlocksInUse(); //.. // 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: //.. // BSLMA_TESTALLOCATOR_EXCEPTION_TEST_BEGIN(ta) //.. // 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. //.. // BSLMA_TESTALLOCATOR_EXCEPTION_TEST_END //.. // 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()); //.. #include <bslscm_version.h> #include <bslalg_arraydestructionprimitives.h> #include <bslma_stdallocator.h> #include <bslmf_assert.h> #include <bslmf_isbitwisemoveable.h> #include <bsls_assert.h> #include <cstddef> // 'std::size_t' #include <cstring> // 'memset', 'memcpy', 'memmove' namespace BloombergLP { namespace bslalg { // ============================= // class AutoArrayMoveDestructor // ============================= template <class OBJECT_TYPE, class ALLOCATOR = bsl::allocator<OBJECT_TYPE> > class AutoArrayMoveDestructor { // This 'class' provides a specialized proctor object that, upon // destruction and unless the 'release' method has been called, bit-wise // moves the elements in a segment of an array of parameterized // 'OBJECT_TYPE' back to some destination, and destroys some other elements // in an adjacent segment of the same array. The elements destroyed are // delimited by the range '[ begin(), middle() )' and those moved to // 'destination()' and in the range '[ middle(), end() )'. Note that, once // constructed, 'begin()' and 'end()' remain fixed. As the guard advances, // 'middle()' and 'destination()' move, reflecting the successful transfer // of data between the moving range and the destination. // DATA OBJECT_TYPE *d_dst_p; // destination of the bit-wise move OBJECT_TYPE *d_begin_p; // address of first element in guarded range OBJECT_TYPE *d_middle_p; // address of first moved element in guarded range // that is also first address beyond last element // destroyed in same guarded range OBJECT_TYPE *d_end_p; // first address beyond last (moved) element in // guarded range ALLOCATOR d_allocator; // allocator // CLASS INVARIANT BSLMF_ASSERT(bslmf::IsBitwiseMoveable<OBJECT_TYPE>::value); private: // NOT IMPLEMENTED AutoArrayMoveDestructor(const AutoArrayMoveDestructor&); AutoArrayMoveDestructor& operator=(const AutoArrayMoveDestructor&); public: // CREATORS AutoArrayMoveDestructor(OBJECT_TYPE *destination, OBJECT_TYPE *begin, OBJECT_TYPE *middle, OBJECT_TYPE *end, ALLOCATOR allocator = ALLOCATOR()); // TBD: document the 'allocator' parameter. // Create a proctor for the sequence of elements of the parameterized // 'OBJECT_TYPE' in the specified range '[ begin, end )' which, upon // destruction, moves the range '[ begin, middle )' to the specified // 'destination' and destroys the '[ middle, end )' range. The // behavior is undefined unless 'begin', 'middle', and 'end' refer to // a contiguous sequence of initialized 'OBJECT_TYPE' objects, where // 'begin <= middle <= end', and 'destination' refers to a contiguous // sequence of (uninitialized) memory of sufficient size to hold // 'end - begin' 'OBJECT_TYPE' objects (which must not overlap // '[begin, end)'). ~AutoArrayMoveDestructor(); // Bit-wise move the range '[ middle(), end() )' to the 'destination()' // address and destroy '[ begin(), middle() )'. // MANIPULATORS void advance(); // Increment both middle and destination pointers by one position. The // behavior is undefined if this operation result in 'destination()' // entering the '[ begin(), end() )' range. // ACCESSORS OBJECT_TYPE *begin() const; // Return the address at the beginning of the guarded range. OBJECT_TYPE *destination() const; // Return the destination address, to which the second portion of the // guarded range, delimited by '[ middle(), end() )', will be moved // upon destruction, or 0 if this guard has been released. OBJECT_TYPE *end() const; // Return the address at the end of the guarded range. OBJECT_TYPE *middle() const; // Return the address at the middle of the guarded range. }; // ============================================================================ // INLINE FUNCTION DEFINITIONS // ============================================================================ // ----------------------------- // class AutoArrayMoveDestructor // ----------------------------- // CREATORS template <class OBJECT_TYPE, class ALLOCATOR> inline AutoArrayMoveDestructor<OBJECT_TYPE, ALLOCATOR>::AutoArrayMoveDestructor( OBJECT_TYPE *destination, OBJECT_TYPE *begin, OBJECT_TYPE *middle, OBJECT_TYPE *end, ALLOCATOR allocator) : d_dst_p(destination) , d_begin_p(begin) , d_middle_p(middle) , d_end_p(end) , d_allocator(allocator) { BSLS_ASSERT_SAFE(!begin == !middle); // neither or both are null BSLS_ASSERT_SAFE(!middle == !end); // neither or both are null BSLS_ASSERT_SAFE(destination || begin == middle); BSLS_ASSERT_SAFE(begin <= middle); BSLS_ASSERT_SAFE(middle <= end); } template <class OBJECT_TYPE, class ALLOCATOR> AutoArrayMoveDestructor<OBJECT_TYPE, ALLOCATOR>::~AutoArrayMoveDestructor() { BSLS_ASSERT_SAFE(!d_begin_p == !d_middle_p); // neither or both are null BSLS_ASSERT_SAFE(!d_middle_p == !d_end_p); // neither or both are null BSLS_ASSERT_SAFE(d_dst_p || d_begin_p == d_middle_p); BSLS_ASSERT_SAFE(d_begin_p <= d_middle_p); BSLS_ASSERT_SAFE(d_middle_p <= d_end_p); BSLS_ASSERT_SAFE(d_dst_p < d_begin_p || d_end_p <= d_dst_p || d_middle_p == d_end_p); if (d_middle_p != d_end_p) { std::size_t numBytes = (char *)d_end_p - (char *)d_middle_p; std::memcpy((void *)d_dst_p, d_middle_p, numBytes); ArrayDestructionPrimitives::destroy(d_begin_p, d_middle_p, d_allocator); } } // MANIPULATORS template <class OBJECT_TYPE, class ALLOCATOR> inline void AutoArrayMoveDestructor<OBJECT_TYPE, ALLOCATOR>::advance() { BSLS_ASSERT_SAFE(d_middle_p < d_end_p); ++d_middle_p; ++d_dst_p; BSLS_ASSERT_SAFE(d_dst_p != d_begin_p || d_middle_p == d_end_p); } // ACCESSORS template <class OBJECT_TYPE, class ALLOCATOR> inline OBJECT_TYPE *AutoArrayMoveDestructor<OBJECT_TYPE, ALLOCATOR>::begin() const { return d_begin_p; } template <class OBJECT_TYPE, class ALLOCATOR> inline OBJECT_TYPE * AutoArrayMoveDestructor<OBJECT_TYPE, ALLOCATOR>::destination() const { return d_dst_p; } template <class OBJECT_TYPE, class ALLOCATOR> inline OBJECT_TYPE *AutoArrayMoveDestructor<OBJECT_TYPE, ALLOCATOR>::end() const { return d_end_p; } template <class OBJECT_TYPE, class ALLOCATOR> inline OBJECT_TYPE *AutoArrayMoveDestructor<OBJECT_TYPE, ALLOCATOR>::middle() const { return d_middle_p; } } // close package namespace #ifndef BDE_OPENSOURCE_PUBLICATION // BACKWARD_COMPATIBILITY // ============================================================================ // BACKWARD COMPATIBILITY // ============================================================================ #ifdef bslalg_AutoArrayMoveDestructor #undef bslalg_AutoArrayMoveDestructor #endif #define bslalg_AutoArrayMoveDestructor bslalg::AutoArrayMoveDestructor // This alias is defined for backward compatibility. #endif // BDE_OPENSOURCE_PUBLICATION -- BACKWARD_COMPATIBILITY } // close enterprise namespace #endif // ---------------------------------------------------------------------------- // Copyright 2013 Bloomberg Finance L.P. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // ----------------------------- END-OF-FILE ----------------------------------