diff --git a/include/pool_allocator/pool_allocator.h b/include/pool_allocator/pool_allocator.h index 6100f6a..c92e45f 100644 --- a/include/pool_allocator/pool_allocator.h +++ b/include/pool_allocator/pool_allocator.h @@ -40,6 +40,19 @@ #include #include +template +struct ExportedAlloc +{ + using pointer = T*; + using size_type = std::size_t; + + // Free slots in the block + std::stack> free_slots; + + // Memory blocks - Optional, only used in _export_all and _import + std::vector memory_blocks; +}; + template class PoolAllocator { @@ -55,35 +68,35 @@ class PoolAllocator using size_type = std::size_t; using difference_type = std::ptrdiff_t; using propagate_on_container_copy_assignment = std::false_type; - using propagate_on_container_move_assignment = std::true_type; + using propagate_on_container_move_assignment = std::false_type; using propagate_on_container_swap = std::true_type; - using is_always_equal = std::true_type; + using is_always_equal = std::false_type; - /* Legacy Rebind struct */ - template - struct rebind - { - typedef PoolAllocator other; - }; + // /* Legacy Rebind struct */ + // template + // struct rebind + // { + // typedef PoolAllocator other; + // }; /* Member functions */ // Default constructor PoolAllocator() noexcept; - // Copy constructor - PoolAllocator(const PoolAllocator& other) noexcept; - // Move constructor - PoolAllocator(PoolAllocator&& other) noexcept; - // Templated copy + // No Copy constructor + PoolAllocator(const PoolAllocator& other) = delete; + // No Move constructor + PoolAllocator(PoolAllocator&& other) = delete; + // No Templated copy template - PoolAllocator(const PoolAllocator& other) noexcept; + PoolAllocator(const PoolAllocator& other) = delete; // Destructor ~PoolAllocator() noexcept; // Assignment operator // We do not allow copy assignment for allocators PoolAllocator& operator=(const PoolAllocator& other) = delete; - // Move assignment operator - PoolAllocator& operator=(PoolAllocator&& other) noexcept; + // We do not allow move assignment for allocators + PoolAllocator& operator=(PoolAllocator&& other) = delete; // Address functions pointer addressof(reference x) const noexcept; @@ -126,30 +139,68 @@ class PoolAllocator // Delete an object void delete_object(pointer p); + // Debug helper functions + // Get total allocated size + inline size_type total_allocated_size() const noexcept + { + return memory_blocks.size() * BlockSize; + } + + // Get total number of free slots + // Does not account for partial blocks + inline size_type total_free_slots() const noexcept + { + return free_slots.size(); + } + + // Transfer free slots from another allocator + void transfer_free(PoolAllocator& from); + // Transfer all memory blocks and free slots from another allocator + void transfer_all(PoolAllocator& from); + private: // Allocate a memory block void allocateBlock(); + // Allocator import/export functions + // Export + //! Export only the available slots as a vector of pointers. + //! Warning: This does NOT transfer ownership of the underlying memory blocks. + //! Do NOT use this function in threads with shorter lifetimes than other threads + //! accessing objects backed by this allocator. Doing so may lead to use-after-free. + ExportedAlloc _export_free(); + + //! Export all the memory blocks + available slots + ExportedAlloc _export_all(); + + // Import + //! Import all memory blocks and free slots from an ExportedAlloc + void _import(ExportedAlloc& exported); + // Pointer to blocks of memory std::vector memory_blocks; size_type current_block_slot = 0; // Current slot in the current block // Free list std::stack> free_slots; + + // Number of items in one block (will be set by the constructor only) + size_type num_items; + size_type item_size; }; // Operators -// Operator != and == -template +// Only two references to the same allocator are equal +template inline bool -operator==(const PoolAllocator&, const PoolAllocator&) noexcept +operator==(const PoolAllocator& a, const PoolAllocator& b) noexcept { - return B1 == B2; + return &a == &b; } -template +template inline bool -operator!=(const PoolAllocator& a, const PoolAllocator& b) noexcept +operator!=(const PoolAllocator& a, const PoolAllocator& b) noexcept { return !(a == b); } diff --git a/include/pool_allocator/pool_allocator.tcc b/include/pool_allocator/pool_allocator.tcc index d3b1c46..ba9effe 100644 --- a/include/pool_allocator/pool_allocator.tcc +++ b/include/pool_allocator/pool_allocator.tcc @@ -38,91 +38,132 @@ template PoolAllocator::PoolAllocator() noexcept { + // Calculate item alignment + constexpr size_type items_per_block = BlockSize / sizeof(T); + static_assert(items_per_block > 0, "Block size is too small for the type T"); + this->num_items = items_per_block; + this->item_size = sizeof(T); } -// Copy constructor +// Destructor template -PoolAllocator::PoolAllocator(const PoolAllocator& other) noexcept +PoolAllocator::~PoolAllocator() noexcept { - // Nothing should be done here + // Free all memory blocks + for (pointer block : memory_blocks) + { + ::operator delete(block, std::align_val_t(alignof(T))); + } } -// Move constructor +// Address functions template -PoolAllocator::PoolAllocator(PoolAllocator&& other) noexcept +typename PoolAllocator::pointer +PoolAllocator::addressof(reference x) const noexcept { - // Move the memory blocks and free slots from the other allocator - memory_blocks = std::move(other.memory_blocks); - free_slots = std::move(other.free_slots); - current_block_slot = other.current_block_slot; + return std::addressof(x); +} - // Clear other allocator's states - other.current_block_slot = 0; +template +typename PoolAllocator::const_pointer +PoolAllocator::addressof(const_reference x) const noexcept +{ + return std::addressof(x); } -// Templated copy template -template -PoolAllocator::PoolAllocator(const PoolAllocator& other) noexcept +inline ExportedAlloc +PoolAllocator::_export_free() { - // Nothing should be done here + ExportedAlloc exported; + exported.free_slots = std::move(free_slots); + // Clear the free slots stack + free_slots = std::stack>(); + + // No memory blocks to export + exported.memory_blocks = std::vector(); + + return exported; } -// Destructor template -PoolAllocator::~PoolAllocator() noexcept +ExportedAlloc +PoolAllocator::_export_all() { - // Free all memory blocks - for (pointer block : memory_blocks) + ExportedAlloc exported; + // Before moving the free slots, unwind the partially free bump-allocation block + // Add its free slots to the exported free slots + if (!memory_blocks.empty() && current_block_slot < num_items) { - ::operator delete(block, std::align_val_t(alignof(T))); + // Convert the partially free (bump allocated) blocks to free slots + for (size_type i = current_block_slot; i < num_items; ++i) + { + // Push the pointer to the free slots stack + exported.free_slots.push(memory_blocks.back() + i); + } } + // Append existing free slots to the unwinded free slots + exported.free_slots.c.insert(exported.free_slots.c.end(), free_slots.c.begin(), + free_slots.c.end()); + // Clear the free slots stack + free_slots = std::stack>(); + + // Move memory blocks to the exported struct + exported.memory_blocks = std::move(memory_blocks); + // Clear the memory blocks (don't free them) + memory_blocks = std::vector(); + + // Reset the current block slot in the allocator + current_block_slot = 0; + + return exported; } -// Move assignment operator template -PoolAllocator& -PoolAllocator::operator=(PoolAllocator&& other) noexcept +void +PoolAllocator::_import(ExportedAlloc& exported) { + // Append the free slots from the exported allocator + free_slots.c.insert(free_slots.c.end(), exported.free_slots.begin(), exported.free_slots.end()); - // Move the memory blocks and free slots from the other allocator - memory_blocks = std::move(other.memory_blocks); - free_slots = std::move(other.free_slots); - current_block_slot = other.current_block_slot; + // We don't need to change the current_block_slot here + // As the imported allocator's partially free slots are already accounted for during export - // Clear other allocator's states - other.current_block_slot = 0; + // Append imported memory blocks from the exported allocator + memory_blocks.insert(memory_blocks.end(), + std::make_move_iterator(exported.memory_blocks.begin()), + std::make_move_iterator(exported.memory_blocks.end())); - return *this; + // Clear the imported memory blocks + exported.memory_blocks = std::vector(); } -// Address functions template -typename PoolAllocator::pointer -PoolAllocator::addressof(reference x) const noexcept +void +PoolAllocator::transfer_all(PoolAllocator& from) { - return std::addressof(x); + assert(&from != this && "Cannot import directly from self"); + + // Export and Import the free slots + auto exported = from._export_all(); + _import(exported); } template -typename PoolAllocator::const_pointer -PoolAllocator::addressof(const_reference x) const noexcept +void +PoolAllocator::transfer_free(PoolAllocator& from) { - return std::addressof(x); + assert(&from != this && "Cannot import directly from self"); + + // Export and Import the free slots + auto exported = from._export_free(); + _import(exported); } template void PoolAllocator::allocateBlock() { - // Calculate item alignment - constexpr size_type num_items = BlockSize / sizeof(T); - - if (num_items < 1) - { - throw std::bad_alloc(); - } - // Allocate a new block of memory pointer new_block = reinterpret_cast(::operator new(BlockSize, std::align_val_t(alignof(T)))); @@ -152,7 +193,7 @@ PoolAllocator::allocate(size_type n) // Handle single object allocation else { - constexpr size_type num_items = BlockSize / sizeof(T); + constexpr size_type items_per_block = BlockSize / sizeof(T); // Check free slots first if (!free_slots.empty()) @@ -163,7 +204,7 @@ PoolAllocator::allocate(size_type n) return p; } // Check current block slot - else if (!memory_blocks.empty() && current_block_slot < num_items) + else if (!memory_blocks.empty() && current_block_slot < items_per_block) { // Increment by 1 pointer p = memory_blocks.back() + current_block_slot;