// Copyright Thorsten Ottosen, 2009. // Distributed under the Boost Software License, Version 1.0. (See // accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #ifndef BOOST_UTILITY_AUTO_BUFFER_HPP_25_02_2009 #define BOOST_UTILITY_AUTO_BUFFER_HPP_25_02_2009 #if defined(_MSC_VER) && (_MSC_VER >= 1200) # pragma once #endif #if BOOST_WORKAROUND(BOOST_MSVC, >= 1400) #pragma warning(push) #pragma warning(disable:4996) #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace boost { // // Policies for creating the stack buffer. // template< unsigned N > struct store_n_objects { BOOST_STATIC_CONSTANT( unsigned, value = N ); }; template< unsigned N > struct store_n_bytes { BOOST_STATIC_CONSTANT( unsigned, value = N ); }; namespace auto_buffer_detail { template< class Policy, class T > struct compute_buffer_size { BOOST_STATIC_CONSTANT( unsigned, value = Policy::value * sizeof(T) ); }; template< unsigned N, class T > struct compute_buffer_size< boost::store_n_bytes, T > { BOOST_STATIC_CONSTANT( unsigned, value = N ); }; template< class Policy, class T > struct compute_buffer_objects { BOOST_STATIC_CONSTANT( unsigned, value = Policy::value ); }; template< unsigned N, class T > struct compute_buffer_objects< boost::store_n_bytes, T > { BOOST_STATIC_CONSTANT( unsigned, value = N / sizeof(T) ); }; } // // As simple scoped buffer class that stores smaller arrays // on the stack and larger on the heap. It can be seen as a // C++ version of C99's variable length arrays, with almost // all its postitive effects, and none of its negative. // // Using this class lead to a major speed-improvement in many // cases. // // The idea originates from Matthew Wilson's stlsoft::auto_buffer, // albeit there are some minor differences. // template< class T, class StackBufferPolicy = boost::store_n_objects<256>, class Allocator = std::allocator > class auto_buffer; template< class T, class StackBufferPolicy, class Allocator > class auto_buffer : Allocator { private: enum { N = auto_buffer_detail:: compute_buffer_objects::value }; BOOST_STATIC_ASSERT( N > 0 ); public: typedef Allocator allocator_type; typedef T value_type; typedef typename Allocator::size_type size_type; typedef typename Allocator::difference_type difference_type; typedef T* pointer; typedef typename Allocator::pointer allocator_pointer; typedef const T* const_pointer; typedef T& reference; typedef const T& const_reference; typedef pointer iterator; typedef const_pointer const_iterator; typedef boost::reverse_iterator reverse_iterator; typedef boost::reverse_iterator const_reverse_iterator; typedef typename boost::mpl::if_c< boost::has_trivial_assign::value && sizeof(T) <= sizeof(long double), const value_type, const_reference >::type optimized_const_reference; private: pointer allocate( size_type capacity ) { if( capacity > N ) return &*get_allocator().allocate( capacity ); else return static_cast( stack_storage_.address() ); } void deallocate( pointer where, size_type capacity ) { if( capacity <= N ) return; get_allocator().deallocate( allocator_pointer(where), capacity ); } template< class I > static void copy_impl( I begin, I end, pointer where, std::random_access_iterator_tag ) { copy_rai( begin, end, where, boost::has_trivial_assign() ); } static void copy_rai( const T* begin, const T* end, pointer where, const boost::true_type& ) { std::memcpy( where, begin, sizeof(T) * std::distance(begin,end) ); } template< class I, bool b > static void copy_rai( I begin, I end, pointer where, const boost::integral_constant& ) { std::uninitialized_copy( begin, end, where ); } template< class I > static void copy_impl( I begin, I end, pointer where, std::forward_iterator_tag ) { std::uninitialized_copy( begin, end, where ); } template< class I > static void copy_impl( I begin, I end, pointer where ) { copy_impl( begin, end, where, typename std::iterator_traits::iterator_category() ); } template< class I, class I2 > static void assign_impl( I begin, I end, I2 where ) { assign_impl( begin, end, where, boost::has_trivial_assign() ); } template< class I, class I2 > static void assign_impl( I begin, I end, I2 where, const boost::true_type& ) { std::memcpy( where, begin, sizeof(T) * std::distance(begin,end) ); } template< class I, class I2 > static void assign_impl( I begin, I end, I2 where, const boost::false_type& ) { for( ; begin != end; ++begin, ++where ) *where = *begin; } void unchecked_push_back_n( size_type n, const boost::true_type& ) { std::uninitialized_fill( end(), end() + n, T() ); } void unchecked_push_back_n( size_type n, const boost::false_type& ) { for( size_type i = 0u; i < n; ++i ) unchecked_push_back(); } void auto_buffer_destroy( pointer where, const boost::false_type& ) { (*where).~T(); } void auto_buffer_destroy( pointer, const boost::true_type& ) { } void auto_buffer_destroy( pointer where ) { auto_buffer_destroy( where, boost::has_trivial_destructor() ); } void destroy_back_n( size_type n, const boost::false_type& ) { pointer buffer = buffer_ + size_ - 1; for( ; buffer >= buffer_; --buffer ) auto_buffer_destroy( buffer ); } void destroy_back_n( size_type n, const boost::true_type& ) { } void auto_buffer_destroy( const boost::false_type& x ) { destroy_back_n( size_, x ); deallocate( buffer_, capacity_ ); } void auto_buffer_destroy( const boost::true_type& ) { deallocate( buffer_, capacity_ ); } pointer move_to_new_buffer( size_type new_capacity, const boost::false_type& ) { pointer new_buffer = allocate( new_capacity ); // strong boost::multi_index::detail::scope_guard guard = boost::multi_index::detail::make_obj_guard( *this, &auto_buffer::deallocate, new_buffer, new_capacity ); copy_impl( begin(), end(), new_buffer ); // strong guard.dismiss(); // nothrow return new_buffer; } pointer move_to_new_buffer( size_type new_capacity, const boost::true_type& ) { pointer new_buffer = allocate( new_capacity ); // strong copy_impl( begin(), end(), new_buffer ); // nothrow return new_buffer; } void reserve_impl( size_type new_capacity ) { pointer new_buffer = move_to_new_buffer( new_capacity, boost::has_nothrow_copy() ); (*this).~auto_buffer(); buffer_ = new_buffer; capacity_ = new_capacity; BOOST_ASSERT( size_ < capacity_ ); } static void swap_helper( auto_buffer& l, auto_buffer& r, const boost::true_type& ) { BOOST_ASSERT( l.is_on_stack() && r.is_on_stack() ); auto_buffer temp( l.begin(), l.end() ); copy_impl( r.begin(), r.end(), l.begin() ); copy_impl( temp.begin(), temp.end(), r.begin() ); boost::swap( l.size_, r.size_ ); boost::swap( l.capacity_, r.capacity_ ); } static void swap_helper( auto_buffer& l, auto_buffer& r, const boost::false_type& ) { BOOST_ASSERT( l.is_on_stack() && r.is_on_stack() ); size_type min_size = l.size_ < r.size_ ? l.size_ : r.size_; size_type max_size = l.size_ > r.size_ ? l.size_ : r.size_; size_type diff = max_size - min_size; auto_buffer* smallest = l.size_ == min_size ? &l : &r; auto_buffer* largest = smallest == &l ? &r : &l; for( unsigned i = 0u; i < diff; ++i ) smallest->unchecked_push_back(); for( size_type i = 0u; i < max_size; ++i ) boost::swap( (*smallest)[i], (*largest)[i] ); largest->pop_back_n( diff ); boost::swap( l.capacity_, r.capacity_ ); } bool is_valid() const { if( buffer_ == 0 ) return false; if( capacity_ < N ) return false; if( !is_on_stack() && capacity_ <= N ) return false; if( buffer_ == stack_storage_.address() ) if( capacity_ > N ) return false; if( size_ > capacity_ ) return false; return true; } public: auto_buffer() : capacity_( N ), buffer_( static_cast(stack_storage_.address()) ), size_( 0 ) { BOOST_ASSERT( is_valid() ); } auto_buffer( const auto_buffer& r ) : capacity_( r.size_ > N ? r.size_ : N ), buffer_( allocate(capacity_) ), size_( 0 ) { copy_impl( r.begin(), r.end(), buffer_ ); size_ = r.size_; BOOST_ASSERT( is_valid() ); } auto_buffer& operator=( const auto_buffer& r ) // basic { BOOST_ASSERT( this != &r ); difference_type diff = size_ - r.size_; if( diff >= 0 ) { pop_back_n( static_cast(diff) ); assign_impl( r.begin(), r.end(), begin() ); } else { if( capacity_ >= r.size() ) { unchecked_push_back_n( static_cast(-diff) ); assign_impl( r.begin(), r.end(), begin() ); } else { pointer new_buffer = allocate( r.size() ); (*this).~auto_buffer(); buffer_ = new_buffer; capacity_ = r.size(); copy_impl( r.begin(), r.end(), buffer_ ); size_ = capacity_; } } BOOST_ASSERT( size() == r.size() ); BOOST_ASSERT( is_valid() ); return *this; } explicit auto_buffer( size_type capacity ) : capacity_( capacity > N ? capacity : N ), buffer_( allocate(capacity_) ), size_( 0 ) { BOOST_ASSERT( is_valid() ); } auto_buffer( size_type capacity, optimized_const_reference init_value ) : capacity_( capacity > N ? capacity : N ), buffer_( allocate(capacity_) ), size_( 0 ) { std::uninitialized_fill( buffer_, buffer_ + capacity, init_value ); size_ = capacity; BOOST_ASSERT( is_valid() ); } auto_buffer( size_type capacity, const allocator_type& a ) : allocator_type( a ), capacity_( capacity > N ? capacity : N ), buffer_( allocate(capacity_) ), size_( 0 ) { BOOST_ASSERT( is_valid() ); } auto_buffer( size_type capacity, optimized_const_reference init_value, const allocator_type& a ) : allocator_type( a ), capacity_( capacity > N ? capacity : N ), buffer_( allocate(capacity_) ), size_( 0 ) { std::uninitialized_fill( buffer_, buffer_ + capacity, init_value ); size_ = capacity; BOOST_ASSERT( is_valid() ); } template< class ForwardIterator > auto_buffer( ForwardIterator begin, ForwardIterator end ) : capacity_( std::distance(begin,end) ), buffer_( allocate(capacity_) ), size_( 0 ) { copy_impl( begin, end, buffer_ ); size_ = capacity_; if( capacity_ < N ) capacity_ = N; BOOST_ASSERT( is_valid() ); } template< class ForwardIterator > auto_buffer( ForwardIterator begin, ForwardIterator end, const allocator_type& a ) : allocator_type( a ), capacity_( std::distance(begin,end) ), buffer_( allocate(capacity_) ), size_( 0 ) { copy_impl( begin, end, buffer_ ); size_ = capacity_; if( capacity_ < N ) capacity_ = N; BOOST_ASSERT( is_valid() ); } ~auto_buffer() { BOOST_ASSERT( is_valid() ); if( buffer_ ) auto_buffer_destroy( boost::has_trivial_destructor() ); } public: bool empty() const { return size_ == 0; } bool full() const { return size_ == capacity_; } bool is_on_stack() const { return capacity_ <= N; } size_type size() const { return size_; } size_type capacity() const { return capacity_; } public: pointer data() { return buffer_; } const_pointer data() const { return buffer_; } allocator_type& get_allocator() { return static_cast(*this); } const allocator_type& get_allocator() const { return static_cast(*this); } public: iterator begin() { return buffer_; } const_iterator begin() const { return buffer_; } iterator end() { return buffer_ + size_; } const_iterator end() const { return buffer_ + size_; } reverse_iterator rbegin() { return reverse_iterator(end()); } const_reverse_iterator rbegin() const { return const_reverse_iterator(end()); } reverse_iterator rend() { return reverse_iterator(begin()); } const_reverse_iterator rend() const { return const_reverse_iterator(begin()); } const_iterator cbegin() const { return const_cast(this)->begin(); } const_iterator cend() const { return const_cast(this)->end(); } const_reverse_iterator crbegin() const { return const_cast(this)->rbegin(); } const_reverse_iterator crend() const { return const_cast(this)->rend(); } public: reference front() { return buffer_[0]; } optimized_const_reference front() const { return buffer_[0]; } reference back() { return buffer_[size_-1]; } optimized_const_reference back() const { return buffer_[size_-1]; } reference operator[]( size_type n ) { BOOST_ASSERT( n < size_ ); return buffer_[n]; } optimized_const_reference operator[]( size_type n ) const { BOOST_ASSERT( n < size_ ); return buffer_[n]; } void unchecked_push_back() { BOOST_ASSERT( !full() ); new (buffer_ + size_) T; ++size_; } void unchecked_push_back_n( size_type n ) { BOOST_ASSERT( size_ + n <= capacity_ ); unchecked_push_back_n( n, boost::has_trivial_assign() ); } void unchecked_push_back( optimized_const_reference x ) // non-growing { BOOST_ASSERT( !full() ); new (buffer_ + size_) T( x ); ++size_; } template< class ForwardIterator > void unchecked_push_back( ForwardIterator begin, ForwardIterator end ) // non-growing { BOOST_ASSERT( size_ + std::distance(begin,end) <= capacity_ ); copy_impl( begin, end, buffer_ + size_ ); size_ += std::distance(begin,end); } void reserve_precisely( size_type n ) { BOOST_ASSERT( capacity_ >= N ); if( n <= capacity_ ) return; reserve_impl( n ); BOOST_ASSERT( capacity_ == n ); } void reserve( size_type n ) // strong { BOOST_ASSERT( capacity_ >= N ); if( n <= capacity_ ) return; // // Remark: we grow the capacity quite agressively. // this is justified since we aim to minimize // heap-allocations, and because we only use // the buffer locally. size_type new_capacity = capacity_ * 2u; new_capacity = new_capacity < n ? n : new_capacity; bool has_wrapped_around = new_capacity < capacity_; if( has_wrapped_around ) { std::length_error e( "Could not expand buffer! Maximum limit exceeded." ); boost::throw_exception( e ); } reserve_impl( new_capacity ); BOOST_ASSERT( capacity_ >= n ); } void push_back() { if( size_ == capacity_ ) reserve( size_ + 1u ); unchecked_push_back(); } void push_back( optimized_const_reference x ) { if( size_ == capacity_ ) reserve( size_ + 1u ); unchecked_push_back( x ); } template< class ForwardIterator > void push_back( ForwardIterator begin, ForwardIterator end ) { difference_type diff = std::distance(begin,end); if( size_ + diff > capacity_ ) reserve( size_ + diff ); unchecked_push_back( begin, end ); } void pop_back() { BOOST_ASSERT( !empty() ); auto_buffer_destroy( buffer_ + size_ - 1u ); --size_; } void pop_back_n( size_type n ) { BOOST_ASSERT( n <= size_ ); destroy_back_n( n, boost::has_trivial_destructor() ); size_ -= n; } void clear() { pop_back_n( size_ ); } pointer uninitialized_grow( size_type n ) // nothrow { BOOST_ASSERT( size_ + n <= capacity_ ); pointer res = end(); size_ += n; return res; } void uninitialized_shrink( size_type n ) // nothrow { // @remark: test for wrap-around BOOST_ASSERT( size_ - n <= capacity_ ); size_ -= n; } void uninitialized_resize( size_type n ) { if( n > size() ) uninitialized_grow( n - size() ); else if( n < size() ) uninitialized_shrink( size() - n ); else ; BOOST_ASSERT( size() == n ); } // nothrow - if both buffer are on the heap, or // - if one buffer is on the heap and one has // 'has_allocated_buffer() == false', or // - if copy-construction cannot throw // basic - otherwise (better guarantee impossible) // requirement: the allocator must be no-throw-swappable void swap( auto_buffer& r ) { bool on_stack = is_on_stack(); bool r_on_stack = r.is_on_stack(); bool both_on_heap = !on_stack && !r_on_stack; if( both_on_heap ) { boost::swap( get_allocator(), r.get_allocator() ); boost::swap( capacity_, r.capacity_ ); boost::swap( buffer_, r.buffer_ ); boost::swap( size_, r.size_ ); BOOST_ASSERT( is_valid() ); BOOST_ASSERT( r.is_valid() ); return; } BOOST_ASSERT( on_stack || r_on_stack ); bool exactly_one_on_stack = (on_stack && !r_on_stack) || (!on_stack && r_on_stack); // // Remark: we now know that we cannot copy into // the unused stack buffer. // if( exactly_one_on_stack ) { auto_buffer* one_on_stack = on_stack ? this : &r; auto_buffer* other = on_stack ? &r : this; pointer new_buffer = static_cast(other->stack_storage_.address()); copy_impl( one_on_stack->begin(), one_on_stack->end(), new_buffer ); // strong one_on_stack->~auto_buffer(); // nothrow boost::swap( get_allocator(), r.get_allocator() ); // assume nothrow boost::swap( capacity_, r.capacity_ ); boost::swap( size_, r.size_ ); one_on_stack->buffer_ = other->buffer_; other->buffer_ = new_buffer; BOOST_ASSERT( other->is_on_stack() ); BOOST_ASSERT( !one_on_stack->is_on_stack() ); BOOST_ASSERT( is_valid() ); BOOST_ASSERT( r.is_valid() ); return; } BOOST_ASSERT( on_stack && r_on_stack ); swap_helper( *this, r, boost::has_trivial_assign() ); BOOST_ASSERT( is_valid() ); BOOST_ASSERT( r.is_valid() ); } private: typedef boost::aligned_storage< N * sizeof(T), boost::alignment_of::value > storage; size_type capacity_; pointer buffer_; size_type size_; storage stack_storage_; }; template< class T, class SBP, class A > inline void swap( auto_buffer& l, auto_buffer& r ) { l.swap( r ); } template< class T, class SBP, class A > inline bool operator==( const auto_buffer& l, const auto_buffer& r ) { if( l.size() != r.size() ) return false; return std::equal( l.begin(), l.end(), r.begin() ); } template< class T, class SBP, class A > inline bool operator!=( const auto_buffer& l, const auto_buffer& r ) { return !(l == r); } template< class T, class SBP, class A > inline bool operator<( const auto_buffer& l, const auto_buffer& r ) { return std::lexicographical_compare( l.begin(), l.end(), r.begin(), r.end() ); } template< class T, class SBP, class A > inline bool operator>( const auto_buffer& l, const auto_buffer& r ) { return (r < l); } template< class T, class SBP, class A > inline bool operator<=( const auto_buffer& l, const auto_buffer& r ) { return !(r > l); } template< class T, class SBP, class A > inline bool operator>=( const auto_buffer& l, const auto_buffer& r ) { return !(l < r); } } #if BOOST_WORKAROUND(BOOST_MSVC, >= 1400) #pragma warning(pop) #endif #endif