/////////////////////////////////////////////////////////////////////////////// // Copyright (c) Electronic Arts Inc. All rights reserved. /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// // This file implements a vector which uses a fixed size memory pool. // The bEnableOverflow template parameter allows the container to resort to // heap allocations if the memory pool is exhausted. /////////////////////////////////////////////////////////////////////////////// #ifndef EASTL_FIXED_VECTOR_H #define EASTL_FIXED_VECTOR_H #include #include #if defined(EA_PRAGMA_ONCE_SUPPORTED) #pragma once // Some compilers (e.g. VC++) benefit significantly from using this. We've measured 3-4% build speed improvements in apps as a result. #endif namespace eastl { /// EASTL_FIXED_VECTOR_DEFAULT_NAME /// /// Defines a default container name in the absence of a user-provided name. /// In the case of fixed-size containers, the allocator name always refers /// to overflow allocations. /// #ifndef EASTL_FIXED_VECTOR_DEFAULT_NAME #define EASTL_FIXED_VECTOR_DEFAULT_NAME EASTL_DEFAULT_NAME_PREFIX " fixed_vector" // Unless the user overrides something, this is "EASTL fixed_vector". #endif /// EASTL_FIXED_VECTOR_DEFAULT_ALLOCATOR /// #ifndef EASTL_FIXED_VECTOR_DEFAULT_ALLOCATOR #define EASTL_FIXED_VECTOR_DEFAULT_ALLOCATOR overflow_allocator_type(EASTL_FIXED_VECTOR_DEFAULT_NAME) #endif /// fixed_vector /// /// A fixed_vector with bEnableOverflow == true is identical to a regular /// vector in terms of its behavior. All the expectations of regular vector /// apply to it and no additional expectations come from it. When bEnableOverflow /// is false, fixed_vector behaves like regular vector with the exception that /// its capacity can never increase. All operations you do on such a fixed_vector /// which require a capacity increase will result in undefined behavior or an /// C++ allocation exception, depending on the configuration of EASTL. /// /// Template parameters: /// T The type of object the vector holds. /// nodeCount The max number of objects to contain. /// bEnableOverflow Whether or not we should use the overflow heap if our object pool is exhausted. /// OverflowAllocator Overflow allocator, which is only used if bEnableOverflow == true. Defaults to the global heap. /// /// Note: The nodeCount value must be at least 1. /// /// Example usage: /// fixed_vector fixedVector); /// /// fixedVector.push_back(Widget()); /// fixedVector.resize(200); /// fixedVector.clear(); /// template ::type> class fixed_vector : public vector > { public: typedef fixed_vector_allocator fixed_allocator_type; typedef OverflowAllocator overflow_allocator_type; typedef vector base_type; typedef fixed_vector this_type; typedef typename base_type::size_type size_type; typedef typename base_type::value_type value_type; typedef typename base_type::reference reference; typedef typename base_type::iterator iterator; typedef typename base_type::const_iterator const_iterator; typedef aligned_buffer aligned_buffer_type; enum { kMaxSize = nodeCount }; using base_type::get_allocator; using base_type::mpBegin; using base_type::mpEnd; using base_type::internalCapacityPtr; using base_type::resize; using base_type::clear; using base_type::size; using base_type::assign; using base_type::npos; using base_type::DoAllocate; using base_type::DoFree; using base_type::DoAssign; using base_type::DoAssignFromIterator; protected: aligned_buffer_type mBuffer; public: fixed_vector(); explicit fixed_vector(const overflow_allocator_type& overflowAllocator); // Only applicable if bEnableOverflow is true. explicit fixed_vector(size_type n); // Currently we don't support overflowAllocator specification for other constructors, for simplicity. fixed_vector(size_type n, const value_type& value); fixed_vector(const this_type& x); fixed_vector(this_type&& x); fixed_vector(this_type&& x, const overflow_allocator_type& overflowAllocator); fixed_vector(std::initializer_list ilist, const overflow_allocator_type& overflowAllocator = EASTL_FIXED_VECTOR_DEFAULT_ALLOCATOR); template fixed_vector(InputIterator first, InputIterator last); this_type& operator=(const this_type& x); this_type& operator=(std::initializer_list ilist); this_type& operator=(this_type&& x); void swap(this_type& x); void set_capacity(size_type n); void clear(bool freeOverflow); void reset_lose_memory(); // This is a unilateral reset to an initially empty state. No destructors are called, no deallocation occurs. size_type max_size() const; // Returns the max fixed size, which is the user-supplied nodeCount parameter. bool full() const; // Returns true if the fixed space has been fully allocated. Note that if overflow is enabled, the container size can be greater than nodeCount but full() could return true because the fixed space may have a recently freed slot. bool has_overflowed() const; // Returns true if the allocations spilled over into the overflow allocator. Meaningful only if overflow is enabled. bool can_overflow() const; // Returns the value of the bEnableOverflow template parameter. void* push_back_uninitialized(); void push_back(const value_type& value); // We implement push_back here because we have a specialization that's reference push_back(); // smaller for the case of overflow being disabled. void push_back(value_type&& value); // OverflowAllocator const overflow_allocator_type& get_overflow_allocator() const EA_NOEXCEPT; overflow_allocator_type& get_overflow_allocator() EA_NOEXCEPT; void set_overflow_allocator(const overflow_allocator_type& allocator); protected: void* DoPushBackUninitialized(true_type); void* DoPushBackUninitialized(false_type); void DoPushBack(true_type, const value_type& value); void DoPushBack(false_type, const value_type& value); void DoPushBackMove(true_type, value_type&& value); void DoPushBackMove(false_type, value_type&& value); reference DoPushBack(false_type); reference DoPushBack(true_type); }; // fixed_vector /////////////////////////////////////////////////////////////////////// // fixed_vector /////////////////////////////////////////////////////////////////////// template inline fixed_vector::fixed_vector() : base_type(fixed_allocator_type(mBuffer.buffer)) { #if EASTL_NAME_ENABLED get_allocator().set_name(EASTL_FIXED_VECTOR_DEFAULT_NAME); #endif mpBegin = mpEnd = (value_type*)&mBuffer.buffer[0]; internalCapacityPtr() = mpBegin + nodeCount; } template inline fixed_vector::fixed_vector(const overflow_allocator_type& overflowAllocator) : base_type(fixed_allocator_type(mBuffer.buffer, overflowAllocator)) { #if EASTL_NAME_ENABLED get_allocator().set_name(EASTL_FIXED_VECTOR_DEFAULT_NAME); #endif mpBegin = mpEnd = (value_type*)&mBuffer.buffer[0]; internalCapacityPtr() = mpBegin + nodeCount; } template inline fixed_vector::fixed_vector(size_type n) : base_type(fixed_allocator_type(mBuffer.buffer)) { #if EASTL_NAME_ENABLED get_allocator().set_name(EASTL_FIXED_VECTOR_DEFAULT_NAME); #endif mpBegin = mpEnd = (value_type*)&mBuffer.buffer[0]; internalCapacityPtr() = mpBegin + nodeCount; resize(n); } template inline fixed_vector::fixed_vector(size_type n, const value_type& value) : base_type(fixed_allocator_type(mBuffer.buffer)) { #if EASTL_NAME_ENABLED get_allocator().set_name(EASTL_FIXED_VECTOR_DEFAULT_NAME); #endif mpBegin = mpEnd = (value_type*)&mBuffer.buffer[0]; internalCapacityPtr() = mpBegin + nodeCount; resize(n, value); } template inline fixed_vector::fixed_vector(const this_type& x) : base_type(fixed_allocator_type(mBuffer.buffer)) { get_allocator().copy_overflow_allocator(x.get_allocator()); #if EASTL_NAME_ENABLED get_allocator().set_name(x.get_allocator().get_name()); #endif mpBegin = mpEnd = (value_type*)&mBuffer.buffer[0]; internalCapacityPtr() = mpBegin + nodeCount; base_type::template DoAssign(x.begin(), x.end(), false_type()); } template inline fixed_vector::fixed_vector(this_type&& x) : base_type(fixed_allocator_type(mBuffer.buffer)) { // Since we are a fixed_vector, we can't swap pointers. We can possibly so something like fixed_swap or // we can just do an assignment from x. If we want to do the former then we need to have some complicated // code to deal with overflow or no overflow, and whether the memory is in the fixed-size buffer or in // the overflow allocator. 90% of the time the memory should be in the fixed buffer, in which case // a simple assignment is no worse than the fancy pathway. // Since we are a fixed_list, we can't normally swap pointers unless both this and // x are using using overflow and the overflow allocators are equal. To do: //if(has_overflowed() && x.has_overflowed() && (get_overflow_allocator() == x.get_overflow_allocator())) //{ // We can swap contents and may need to swap the allocators as well. //} // The following is currently identical to the fixed_vector(const this_type& x) code above. If it stays that // way then we may want to make a shared implementation. get_allocator().copy_overflow_allocator(x.get_allocator()); #if EASTL_NAME_ENABLED get_allocator().set_name(x.get_allocator().get_name()); #endif mpBegin = mpEnd = (value_type*)&mBuffer.buffer[0]; internalCapacityPtr() = mpBegin + nodeCount; base_type::template DoAssign, true>(eastl::make_move_iterator(x.begin()), eastl::make_move_iterator(x.end()), false_type()); } template inline fixed_vector::fixed_vector(this_type&& x, const overflow_allocator_type& overflowAllocator) : base_type(fixed_allocator_type(mBuffer.buffer, overflowAllocator)) { // See the discussion above. // The following is currently identical to the fixed_vector(const this_type& x) code above. If it stays that // way then we may want to make a shared implementation. get_allocator().copy_overflow_allocator(x.get_allocator()); #if EASTL_NAME_ENABLED get_allocator().set_name(x.get_allocator().get_name()); #endif mpBegin = mpEnd = (value_type*)&mBuffer.buffer[0]; internalCapacityPtr() = mpBegin + nodeCount; base_type::template DoAssign(x.begin(), x.end(), false_type()); } template inline fixed_vector::fixed_vector(std::initializer_list ilist, const overflow_allocator_type& overflowAllocator) : base_type(fixed_allocator_type(mBuffer.buffer, overflowAllocator)) { typedef typename std::initializer_list::iterator InputIterator; typedef typename eastl::iterator_traits::iterator_category IC; mpBegin = mpEnd = (value_type*)&mBuffer.buffer[0]; internalCapacityPtr() = mpBegin + nodeCount; base_type::template DoAssignFromIterator(ilist.begin(), ilist.end(), IC()); } template template fixed_vector::fixed_vector(InputIterator first, InputIterator last) : base_type(fixed_allocator_type(mBuffer.buffer)) { #if EASTL_NAME_ENABLED get_allocator().set_name(EASTL_FIXED_VECTOR_DEFAULT_NAME); #endif mpBegin = mpEnd = (value_type*)&mBuffer.buffer[0]; internalCapacityPtr() = mpBegin + nodeCount; base_type::template DoAssign(first, last, is_integral()); } template inline typename fixed_vector::this_type& fixed_vector::operator=(const this_type& x) { if(this != &x) { clear(); #if EASTL_ALLOCATOR_COPY_ENABLED get_allocator() = x.get_allocator(); // The primary effect of this is to copy the overflow allocator. #endif base_type::template DoAssign(x.begin(), x.end(), false_type()); // Shorter route. } return *this; } template inline typename fixed_vector::this_type& fixed_vector::operator=(std::initializer_list ilist) { typedef typename std::initializer_list::iterator InputIterator; typedef typename eastl::iterator_traits::iterator_category IC; clear(); base_type::template DoAssignFromIterator(ilist.begin(), ilist.end(), IC()); return *this; } template inline typename fixed_vector::this_type& fixed_vector::operator=(this_type&& x) { // Since we are a fixed_vector, we can't swap pointers. We can possibly do something like fixed_swap or // we can just do an assignment from x. If we want to do the former then we need to have some complicated // code to deal with overflow or no overflow, and whether the memory is in the fixed-size buffer or in // the overflow allocator. 90% of the time the memory should be in the fixed buffer, in which case // a simple assignment is no worse than the fancy pathway. if (this != &x) { clear(); #if EASTL_ALLOCATOR_COPY_ENABLED get_allocator() = x.get_allocator(); // The primary effect of this is to copy the overflow allocator. #endif base_type::template DoAssign, true>(eastl::make_move_iterator(x.begin()), eastl::make_move_iterator(x.end()), false_type()); // Shorter route. } return *this; } template inline void fixed_vector::swap(this_type& x) { if((has_overflowed() && x.has_overflowed()) && (get_overflow_allocator() == x.get_overflow_allocator())) // If both containers are using the heap instead of local memory { // then we can do a fast pointer swap instead of content swap. eastl::swap(mpBegin, x.mpBegin); eastl::swap(mpEnd, x.mpEnd); eastl::swap(internalCapacityPtr(), x.internalCapacityPtr()); } else { // Fixed containers use a special swap that can deal with excessively large buffers. eastl::fixed_swap(*this, x); } } template inline void fixed_vector::set_capacity(size_type n) { const size_type nPrevSize = (size_type)(mpEnd - mpBegin); const size_type nPrevCapacity = (size_type)(internalCapacityPtr() - mpBegin); if(n == npos) // If the user means to set the capacity so that it equals the size (i.e. free excess capacity)... n = nPrevSize; if(n != nPrevCapacity) // If the request results in a capacity change... { if(can_overflow() && (((uintptr_t)mpBegin != (uintptr_t)mBuffer.buffer) || (n > kMaxSize))) // If we are or would be using dynamically allocated memory instead of our fixed-size member buffer... { T* const pNewData = (n <= kMaxSize) ? (T*)&mBuffer.buffer[0] : DoAllocate(n); T* const pCopyEnd = (n < nPrevSize) ? (mpBegin + n) : mpEnd; eastl::uninitialized_move_ptr(mpBegin, pCopyEnd, pNewData); // Move [mpBegin, pCopyEnd) to p. eastl::destruct(mpBegin, mpEnd); if((uintptr_t)mpBegin != (uintptr_t)mBuffer.buffer) DoFree(mpBegin, (size_type)(internalCapacityPtr() - mpBegin)); mpEnd = pNewData + (pCopyEnd - mpBegin); mpBegin = pNewData; internalCapacityPtr() = mpBegin + n; } // Else the new capacity would be within our fixed buffer. else if(n < nPrevSize) // If the newly requested capacity is less than our size, we do what vector::set_capacity does and resize, even though we actually aren't reducing the capacity. resize(n); } } template inline void fixed_vector::clear(bool freeOverflow) { base_type::clear(); if (freeOverflow && mpBegin != (value_type*)&mBuffer.buffer[0]) { EASTLFree(get_allocator(), mpBegin, (internalCapacityPtr() - mpBegin) * sizeof(T)); mpBegin = mpEnd = (value_type*)&mBuffer.buffer[0]; internalCapacityPtr() = mpBegin + nodeCount; } } template inline void fixed_vector::reset_lose_memory() { mpBegin = mpEnd = (value_type*)&mBuffer.buffer[0]; internalCapacityPtr() = mpBegin + nodeCount; } template inline typename fixed_vector::size_type fixed_vector::max_size() const { return kMaxSize; } template inline bool fixed_vector::full() const { // If size >= capacity, then we are definitely full. // Also, if our size is smaller but we've switched away from mBuffer due to a previous overflow, then we are considered full. return ((size_t)(mpEnd - mpBegin) >= kMaxSize) || ((void*)mpBegin != (void*)mBuffer.buffer); } template inline bool fixed_vector::has_overflowed() const { // This will be incorrect for the case that bOverflowEnabled is true and the container was resized // down to a small size where the fixed buffer could take over ownership of the data again. // The only simple fix for this is to take on another member variable which tracks whether this overflow // has occurred at some point in the past. return ((void*)mpBegin != (void*)mBuffer.buffer); } template inline bool fixed_vector::can_overflow() const { return bEnableOverflow; } template inline void* fixed_vector::push_back_uninitialized() { return DoPushBackUninitialized(typename type_select::type()); } template inline void* fixed_vector::DoPushBackUninitialized(true_type) { return base_type::push_back_uninitialized(); } template inline void* fixed_vector::DoPushBackUninitialized(false_type) { EASTL_ASSERT(mpEnd < internalCapacityPtr()); return mpEnd++; } template inline void fixed_vector::push_back(const value_type& value) { DoPushBack(typename type_select::type(), value); } template inline void fixed_vector::DoPushBack(true_type, const value_type& value) { base_type::push_back(value); } // This template specializes for overflow NOT enabled. // In this configuration, there is no need for the heavy weight push_back() which tests to see if the container should grow (it never will) template inline void fixed_vector::DoPushBack(false_type, const value_type& value) { EASTL_ASSERT(mpEnd < internalCapacityPtr()); ::new((void*)mpEnd++) value_type(value); } template inline typename fixed_vector::reference fixed_vector::push_back() { return DoPushBack(typename type_select::type()); } template inline typename fixed_vector::reference fixed_vector::DoPushBack(true_type) { return base_type::push_back(); } // This template specializes for overflow NOT enabled. // In this configuration, there is no need for the heavy weight push_back() which tests to see if the container should grow (it never will) template inline typename fixed_vector::reference fixed_vector::DoPushBack(false_type) { EASTL_ASSERT(mpEnd < internalCapacityPtr()); ::new((void*)mpEnd++) value_type; // Note that this isn't value_type() as that syntax doesn't work on all compilers for POD types. return *(mpEnd - 1); // Same as return back(); } template inline void fixed_vector::push_back(value_type&& value) { DoPushBackMove(typename type_select::type(), eastl::move(value)); } template inline void fixed_vector::DoPushBackMove(true_type, value_type&& value) { base_type::push_back(eastl::move(value)); // This will call vector::push_back(value_type &&), and possibly swap value with *mpEnd. } // This template specializes for overflow NOT enabled. // In this configuration, there is no need for the heavy weight push_back() which tests to see if the container should grow (it never will) template inline void fixed_vector::DoPushBackMove(false_type, value_type&& value) { EASTL_ASSERT(mpEnd < internalCapacityPtr()); ::new((void*)mpEnd++) value_type(eastl::move(value)); // This will call the value_type(value_type&&) constructor, and possibly swap value with *mpEnd. } template inline const typename fixed_vector::overflow_allocator_type& fixed_vector::get_overflow_allocator() const EA_NOEXCEPT { return get_allocator().get_overflow_allocator(); } template inline typename fixed_vector::overflow_allocator_type& fixed_vector::get_overflow_allocator() EA_NOEXCEPT { return get_allocator().get_overflow_allocator(); } template inline void fixed_vector::set_overflow_allocator(const overflow_allocator_type& allocator) { get_allocator().set_overflow_allocator(allocator); } /////////////////////////////////////////////////////////////////////// // global operators /////////////////////////////////////////////////////////////////////// // operator ==, !=, <, >, <=, >= come from the vector implementations. template inline void swap(fixed_vector& a, fixed_vector& b) { // Fixed containers use a special swap that can deal with excessively large buffers. eastl::fixed_swap(a, b); } } // namespace eastl #endif // Header include guard