From 8735bbcbf9c7fcb10aa570d19419f89c8cf64149 Mon Sep 17 00:00:00 2001 From: Canlin Zhang Date: Thu, 7 Aug 2025 21:53:51 +0000 Subject: [PATCH 01/15] Implemente import/export functions --- include/pool_allocator/pool_allocator.h | 94 +++++++++++--- include/pool_allocator/pool_allocator.tcc | 147 ++++++++++++++++------ 2 files changed, 187 insertions(+), 54 deletions(-) diff --git a/include/pool_allocator/pool_allocator.h b/include/pool_allocator/pool_allocator.h index 6100f6a..226de51 100644 --- a/include/pool_allocator/pool_allocator.h +++ b/include/pool_allocator/pool_allocator.h @@ -37,9 +37,25 @@ #include #include #include +#include #include #include +template +struct ExportedAlloc +{ + using pointer = T*; + using size_type = std::size_t; + + // Free slots in the block + std::vector free_slots; + + // Memory blocks - Optional, only used in export_all and import_all + std::optional> memory_blocks; + // Bump allocation counter - Optional, only used in export_all and import_all + std::optional current_block_slot; +}; + template class PoolAllocator { @@ -55,35 +71,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,6 +142,37 @@ 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(); + } + + // Allocator import/export functions + // Export + //! Export only the available slots as a vector of pointers + ExportedAlloc export_free(); + + //! Export all the memory blocks + available slots + ExportedAlloc export_all(); + + // Import + //! Import free slots from an ExportedAlloc + void import_free(const ExportedAlloc& exported); + void import_free(PoolAllocator& from); + + //! Import all memory blocks and free slots from an ExportedAlloc + void import_all(const ExportedAlloc& exported); + void import_all(PoolAllocator& from); + private: // Allocate a memory block void allocateBlock(); @@ -147,6 +194,14 @@ operator==(const PoolAllocator&, const PoolAllocator&) noexcept return B1 == B2; } +// Only two references to the same allocator are equal +template +inline bool +operator==(const PoolAllocator& a, const PoolAllocator& b) noexcept +{ + return &a == &b; +} + template inline bool operator!=(const PoolAllocator& a, const PoolAllocator& b) noexcept @@ -154,5 +209,12 @@ operator!=(const PoolAllocator& a, const PoolAllocator& b) noexc return !(a == b); } +template +inline bool +operator!=(const PoolAllocator& a, const PoolAllocator& b) noexcept +{ + return !(a == b); +} + // include the implementation file #include "pool_allocator.tcc" diff --git a/include/pool_allocator/pool_allocator.tcc b/include/pool_allocator/pool_allocator.tcc index d3b1c46..3e22d60 100644 --- a/include/pool_allocator/pool_allocator.tcc +++ b/include/pool_allocator/pool_allocator.tcc @@ -40,75 +40,146 @@ PoolAllocator::PoolAllocator() noexcept { } -// 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; - - // Clear other allocator's states - other.current_block_slot = 0; + return std::addressof(x); } -// Templated copy template -template -PoolAllocator::PoolAllocator(const PoolAllocator& other) noexcept +typename PoolAllocator::const_pointer +PoolAllocator::addressof(const_reference x) const noexcept { - // Nothing should be done here + return std::addressof(x); } -// Destructor template -PoolAllocator::~PoolAllocator() noexcept +inline ExportedAlloc +PoolAllocator::export_free() { - // Free all memory blocks - for (pointer block : memory_blocks) + ExportedAlloc exported; + exported.free_slots = std::move(free_slots); + // Clear the free slots stack + while (!free_slots.empty()) { - ::operator delete(block, std::align_val_t(alignof(T))); + free_slots.pop(); } + + // No memory blocks to export + exported.memory_blocks = std::nullopt; + // No current block slot to export + exported.current_block_slot = std::nullopt; + + return exported; } -// Move assignment operator template -PoolAllocator& -PoolAllocator::operator=(PoolAllocator&& other) noexcept +ExportedAlloc +PoolAllocator::export_all() { + ExportedAlloc exported; + // Move the free slots to the exported struct + exported.free_slots = std::move(free_slots); + // Clear the free slots stack + while (!free_slots.empty()) + { + free_slots.pop(); + } - // 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; + // Move memory blocks to the exported struct + exported.memory_blocks = std::move(memory_blocks); + // Clear the memory blocks (don't free them) + memory_blocks.clear(); - // Clear other allocator's states - other.current_block_slot = 0; + // Move the current block slot to the exported struct + exported.current_block_slot = current_block_slot; + // Reset the current block slot in the allocator + current_block_slot = 0; - return *this; + return exported; } -// Address functions template -typename PoolAllocator::pointer -PoolAllocator::addressof(reference x) const noexcept +void +PoolAllocator::import_free(const ExportedAlloc& exported) { - return std::addressof(x); + // Append the free slots from the exported allocator + for (const auto& slot : exported.free_slots) + { + free_slots.push(slot); + } } template -typename PoolAllocator::const_pointer -PoolAllocator::addressof(const_reference x) const noexcept +void +PoolAllocator::import_free(PoolAllocator& from) { - return std::addressof(x); + // Export and Import the free slots + auto exported = from.export_free(); + import_free(exported); +} + +template +void +PoolAllocator::import_all(const ExportedAlloc& exported) +{ + // Sanity check + assert(exported.memory_blocks.has_value() && exported.current_block_slot.has_value()); + + // Import free slots + import_free(exported); + + // Compare the bump allocator counter + // If the current has more free slots (smaller counter), we append the new memory blocks at the + // back. + if (!memory_blocks.empty()) + { + if (exported.current_block_slot.value() < current_block_slot) + { + // Append the memory blocks at the back of the current memory blocks + memory_blocks.insert(memory_blocks.end(), exported.memory_blocks->begin(), + exported.memory_blocks->end()); + // Update the current block slot + current_block_slot = exported.current_block_slot.value(); + } + else + { + // Prepend the memory blocks at the front of the current memory blocks + memory_blocks.insert(memory_blocks.begin(), exported.memory_blocks->begin(), + exported.memory_blocks->end()); + // We keep the current block slot as is + } + } + else + { + // If the current memory blocks are empty, we just move the memory blocks from the exported + // allocator + memory_blocks.insert(memory_blocks.end(), exported.memory_blocks->begin(), + exported.memory_blocks->end()); + current_block_slot = exported.current_block_slot.value(); + } +} + +template +void +PoolAllocator::import_all(PoolAllocator& from) +{ + // Export and Import the free slots + auto exported = from.export_all(); + import_all(exported); } template From 894039a3cbb21f08f375715ce90c01635bf4e042 Mon Sep 17 00:00:00 2001 From: Canlin Zhang Date: Thu, 7 Aug 2025 22:36:32 +0000 Subject: [PATCH 02/15] minor fixes --- include/pool_allocator/pool_allocator.h | 5 ++++- include/pool_allocator/pool_allocator.tcc | 10 ++-------- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/include/pool_allocator/pool_allocator.h b/include/pool_allocator/pool_allocator.h index 226de51..d691079 100644 --- a/include/pool_allocator/pool_allocator.h +++ b/include/pool_allocator/pool_allocator.h @@ -158,7 +158,10 @@ class PoolAllocator // Allocator import/export functions // Export - //! Export only the available slots as a vector of pointers + //! 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 diff --git a/include/pool_allocator/pool_allocator.tcc b/include/pool_allocator/pool_allocator.tcc index 3e22d60..6f411dd 100644 --- a/include/pool_allocator/pool_allocator.tcc +++ b/include/pool_allocator/pool_allocator.tcc @@ -73,10 +73,7 @@ PoolAllocator::export_free() ExportedAlloc exported; exported.free_slots = std::move(free_slots); // Clear the free slots stack - while (!free_slots.empty()) - { - free_slots.pop(); - } + free_slots = std::stack>(); // No memory blocks to export exported.memory_blocks = std::nullopt; @@ -94,10 +91,7 @@ PoolAllocator::export_all() // Move the free slots to the exported struct exported.free_slots = std::move(free_slots); // Clear the free slots stack - while (!free_slots.empty()) - { - free_slots.pop(); - } + free_slots = std::stack>(); // Move memory blocks to the exported struct exported.memory_blocks = std::move(memory_blocks); From 71069b0e72143b8d5a7524a0b53e92c2a4232f4f Mon Sep 17 00:00:00 2001 From: Canlin Zhang Date: Thu, 7 Aug 2025 22:44:30 +0000 Subject: [PATCH 03/15] quick type fix --- include/pool_allocator/pool_allocator.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/pool_allocator/pool_allocator.h b/include/pool_allocator/pool_allocator.h index d691079..dfd9d9f 100644 --- a/include/pool_allocator/pool_allocator.h +++ b/include/pool_allocator/pool_allocator.h @@ -48,7 +48,7 @@ struct ExportedAlloc using size_type = std::size_t; // Free slots in the block - std::vector free_slots; + std::stack free_slots; // Memory blocks - Optional, only used in export_all and import_all std::optional> memory_blocks; From 2d0eb3decfcc46fab4109d95843ba88d6e493230 Mon Sep 17 00:00:00 2001 From: Canlin Zhang Date: Thu, 7 Aug 2025 22:45:56 +0000 Subject: [PATCH 04/15] Fix typing error --- include/pool_allocator/pool_allocator.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/pool_allocator/pool_allocator.h b/include/pool_allocator/pool_allocator.h index dfd9d9f..d8fbb16 100644 --- a/include/pool_allocator/pool_allocator.h +++ b/include/pool_allocator/pool_allocator.h @@ -48,10 +48,10 @@ struct ExportedAlloc using size_type = std::size_t; // Free slots in the block - std::stack free_slots; + std::stack> free_slots; // Memory blocks - Optional, only used in export_all and import_all - std::optional> memory_blocks; + std::optional memory_blocks; // Bump allocation counter - Optional, only used in export_all and import_all std::optional current_block_slot; }; From fe5744abaf4c9116d71061268aee03539cf54569 Mon Sep 17 00:00:00 2001 From: Canlin Zhang Date: Thu, 7 Aug 2025 22:48:52 +0000 Subject: [PATCH 05/15] another typing error fix --- include/pool_allocator/pool_allocator.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/pool_allocator/pool_allocator.h b/include/pool_allocator/pool_allocator.h index d8fbb16..9f82ec5 100644 --- a/include/pool_allocator/pool_allocator.h +++ b/include/pool_allocator/pool_allocator.h @@ -51,7 +51,7 @@ struct ExportedAlloc std::stack> free_slots; // Memory blocks - Optional, only used in export_all and import_all - std::optional memory_blocks; + std::optional> memory_blocks; // Bump allocation counter - Optional, only used in export_all and import_all std::optional current_block_slot; }; From f6ae894ca4fcce16d2f9df78f70a2e5ec3dcb592 Mon Sep 17 00:00:00 2001 From: Canlin Zhang Date: Thu, 7 Aug 2025 22:55:20 +0000 Subject: [PATCH 06/15] Guard against self import (direct) --- include/pool_allocator/pool_allocator.tcc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/include/pool_allocator/pool_allocator.tcc b/include/pool_allocator/pool_allocator.tcc index 6f411dd..d9ccfa3 100644 --- a/include/pool_allocator/pool_allocator.tcc +++ b/include/pool_allocator/pool_allocator.tcc @@ -121,6 +121,8 @@ template void PoolAllocator::import_free(PoolAllocator& from) { + assert(&from != this && "Cannot import directly from self"); + // Export and Import the free slots auto exported = from.export_free(); import_free(exported); @@ -171,6 +173,8 @@ template void PoolAllocator::import_all(PoolAllocator& from) { + assert(&from != this && "Cannot import directly from self"); + // Export and Import the free slots auto exported = from.export_all(); import_all(exported); From d98833e753dd2b57d32473634b8e9534b92ebcb4 Mon Sep 17 00:00:00 2001 From: Canlin Zhang Date: Thu, 7 Aug 2025 23:00:02 +0000 Subject: [PATCH 07/15] Fix undefined behavior on clearing moved blocks --- include/pool_allocator/pool_allocator.tcc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/pool_allocator/pool_allocator.tcc b/include/pool_allocator/pool_allocator.tcc index d9ccfa3..580234d 100644 --- a/include/pool_allocator/pool_allocator.tcc +++ b/include/pool_allocator/pool_allocator.tcc @@ -96,7 +96,7 @@ PoolAllocator::export_all() // Move memory blocks to the exported struct exported.memory_blocks = std::move(memory_blocks); // Clear the memory blocks (don't free them) - memory_blocks.clear(); + memory_blocks = std::vector(); // Move the current block slot to the exported struct exported.current_block_slot = current_block_slot; From a88e46e19bacfcb8d278de14226b3e075e6ea041 Mon Sep 17 00:00:00 2001 From: Canlin Zhang Date: Fri, 8 Aug 2025 00:27:24 +0000 Subject: [PATCH 08/15] Various fixes for review --- include/pool_allocator/pool_allocator.h | 13 ++-- include/pool_allocator/pool_allocator.tcc | 72 +++++++++++------------ 2 files changed, 41 insertions(+), 44 deletions(-) diff --git a/include/pool_allocator/pool_allocator.h b/include/pool_allocator/pool_allocator.h index 9f82ec5..36eca2b 100644 --- a/include/pool_allocator/pool_allocator.h +++ b/include/pool_allocator/pool_allocator.h @@ -37,7 +37,6 @@ #include #include #include -#include #include #include @@ -51,9 +50,9 @@ struct ExportedAlloc std::stack> free_slots; // Memory blocks - Optional, only used in export_all and import_all - std::optional> memory_blocks; + std::vector memory_blocks; // Bump allocation counter - Optional, only used in export_all and import_all - std::optional current_block_slot; + size_type current_block_slot; }; template @@ -169,11 +168,11 @@ class PoolAllocator // Import //! Import free slots from an ExportedAlloc - void import_free(const ExportedAlloc& exported); + void import_free(ExportedAlloc& exported); void import_free(PoolAllocator& from); //! Import all memory blocks and free slots from an ExportedAlloc - void import_all(const ExportedAlloc& exported); + void import_all(ExportedAlloc& exported); void import_all(PoolAllocator& from); private: @@ -186,6 +185,10 @@ class PoolAllocator // 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 diff --git a/include/pool_allocator/pool_allocator.tcc b/include/pool_allocator/pool_allocator.tcc index 580234d..8030c5c 100644 --- a/include/pool_allocator/pool_allocator.tcc +++ b/include/pool_allocator/pool_allocator.tcc @@ -38,6 +38,11 @@ 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); } // Destructor @@ -76,9 +81,9 @@ PoolAllocator::export_free() free_slots = std::stack>(); // No memory blocks to export - exported.memory_blocks = std::nullopt; + exported.memory_blocks = std::vector(); // No current block slot to export - exported.current_block_slot = std::nullopt; + exported.current_block_slot = 0; return exported; } @@ -108,13 +113,16 @@ PoolAllocator::export_all() template void -PoolAllocator::import_free(const ExportedAlloc& exported) +PoolAllocator::import_free(ExportedAlloc& exported) { // Append the free slots from the exported allocator for (const auto& slot : exported.free_slots) { free_slots.push(slot); } + + // Clear the imported free slots to avoid potential reuse + exported.free_slots = std::stack>(); } template @@ -130,43 +138,37 @@ PoolAllocator::import_free(PoolAllocator& from) template void -PoolAllocator::import_all(const ExportedAlloc& exported) +PoolAllocator::import_all(ExportedAlloc& exported) { - // Sanity check - assert(exported.memory_blocks.has_value() && exported.current_block_slot.has_value()); - // Import free slots import_free(exported); // Compare the bump allocator counter // If the current has more free slots (smaller counter), we append the new memory blocks at the // back. - if (!memory_blocks.empty()) + if (!exported.memory_blocks.empty()) { - if (exported.current_block_slot.value() < current_block_slot) + // Convert the partially free (bump allocated) blocks from the imported allocator to free + // slots + for (size_type i = exported.current_block_slot; i < this->num_items; ++i) { - // Append the memory blocks at the back of the current memory blocks - memory_blocks.insert(memory_blocks.end(), exported.memory_blocks->begin(), - exported.memory_blocks->end()); - // Update the current block slot - current_block_slot = exported.current_block_slot.value(); + // Push the pointer to the free slots stack + free_slots.push(exported.memory_blocks.back() + i * this->item_size); } - else - { - // Prepend the memory blocks at the front of the current memory blocks - memory_blocks.insert(memory_blocks.begin(), exported.memory_blocks->begin(), - exported.memory_blocks->end()); - // We keep the current block slot as is - } - } - else - { - // If the current memory blocks are empty, we just move the memory blocks from the exported - // allocator - memory_blocks.insert(memory_blocks.end(), exported.memory_blocks->begin(), - exported.memory_blocks->end()); - current_block_slot = exported.current_block_slot.value(); + + // Always append the new memory blocks at the front so we don't change the back blocks + memory_blocks.insert(memory_blocks.begin(), exported.memory_blocks.begin(), + exported.memory_blocks.end()); } + + // We don't need to change the current_block_slot here + // As the imported allocator's partially free slots are already accounted for + + // Clean up the imported structure to avoid potential reuse + // Clear the imported memory blocks + exported.memory_blocks = std::vector(); + // Reset the imported current block slot + exported.current_block_slot = 0; } template @@ -184,14 +186,6 @@ 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)))); @@ -221,7 +215,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()) @@ -232,7 +226,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; From ea5345a30728099db3596bdbafa450211eca76d7 Mon Sep 17 00:00:00 2001 From: Canlin Zhang Date: Fri, 8 Aug 2025 10:11:41 -0700 Subject: [PATCH 09/15] Update include/pool_allocator/pool_allocator.tcc Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- include/pool_allocator/pool_allocator.tcc | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/include/pool_allocator/pool_allocator.tcc b/include/pool_allocator/pool_allocator.tcc index 8030c5c..bc206f8 100644 --- a/include/pool_allocator/pool_allocator.tcc +++ b/include/pool_allocator/pool_allocator.tcc @@ -115,11 +115,10 @@ template void PoolAllocator::import_free(ExportedAlloc& exported) { - // Append the free slots from the exported allocator - for (const auto& slot : exported.free_slots) - { - free_slots.push(slot); - } + // Append the free slots from the exported allocator efficiently + free_slots.c.insert(free_slots.c.end(), + std::make_move_iterator(exported.free_slots.c.begin()), + std::make_move_iterator(exported.free_slots.c.end())); // Clear the imported free slots to avoid potential reuse exported.free_slots = std::stack>(); From 9390b2e7a80e756040378dd1b48f0f09bf4ac65a Mon Sep 17 00:00:00 2001 From: Canlin Zhang Date: Fri, 8 Aug 2025 10:12:11 -0700 Subject: [PATCH 10/15] Update include/pool_allocator/pool_allocator.tcc Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- include/pool_allocator/pool_allocator.tcc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/pool_allocator/pool_allocator.tcc b/include/pool_allocator/pool_allocator.tcc index bc206f8..69cc87c 100644 --- a/include/pool_allocator/pool_allocator.tcc +++ b/include/pool_allocator/pool_allocator.tcc @@ -152,7 +152,7 @@ PoolAllocator::import_all(ExportedAlloc& exported) for (size_type i = exported.current_block_slot; i < this->num_items; ++i) { // Push the pointer to the free slots stack - free_slots.push(exported.memory_blocks.back() + i * this->item_size); + free_slots.push(exported.memory_blocks.back() + i); } // Always append the new memory blocks at the front so we don't change the back blocks From 92c35d783ec40093002160c8bb7c70dd5f689c29 Mon Sep 17 00:00:00 2001 From: Canlin Zhang Date: Fri, 8 Aug 2025 18:27:27 +0000 Subject: [PATCH 11/15] Removed index from exported struct --- include/pool_allocator/pool_allocator.h | 2 -- include/pool_allocator/pool_allocator.tcc | 44 ++++++++++------------- 2 files changed, 18 insertions(+), 28 deletions(-) diff --git a/include/pool_allocator/pool_allocator.h b/include/pool_allocator/pool_allocator.h index 36eca2b..fe0ce32 100644 --- a/include/pool_allocator/pool_allocator.h +++ b/include/pool_allocator/pool_allocator.h @@ -51,8 +51,6 @@ struct ExportedAlloc // Memory blocks - Optional, only used in export_all and import_all std::vector memory_blocks; - // Bump allocation counter - Optional, only used in export_all and import_all - size_type current_block_slot; }; template diff --git a/include/pool_allocator/pool_allocator.tcc b/include/pool_allocator/pool_allocator.tcc index 69cc87c..dcab5a0 100644 --- a/include/pool_allocator/pool_allocator.tcc +++ b/include/pool_allocator/pool_allocator.tcc @@ -93,6 +93,17 @@ ExportedAlloc PoolAllocator::export_all() { 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) + { + // 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); + } + } // Move the free slots to the exported struct exported.free_slots = std::move(free_slots); // Clear the free slots stack @@ -103,8 +114,6 @@ PoolAllocator::export_all() // Clear the memory blocks (don't free them) memory_blocks = std::vector(); - // Move the current block slot to the exported struct - exported.current_block_slot = current_block_slot; // Reset the current block slot in the allocator current_block_slot = 0; @@ -116,8 +125,7 @@ void PoolAllocator::import_free(ExportedAlloc& exported) { // Append the free slots from the exported allocator efficiently - free_slots.c.insert(free_slots.c.end(), - std::make_move_iterator(exported.free_slots.c.begin()), + free_slots.c.insert(free_slots.c.end(), std::make_move_iterator(exported.free_slots.c.begin()), std::make_move_iterator(exported.free_slots.c.end())); // Clear the imported free slots to avoid potential reuse @@ -142,32 +150,16 @@ PoolAllocator::import_all(ExportedAlloc& exported) // Import free slots import_free(exported); - // Compare the bump allocator counter - // If the current has more free slots (smaller counter), we append the new memory blocks at the - // back. - if (!exported.memory_blocks.empty()) - { - // Convert the partially free (bump allocated) blocks from the imported allocator to free - // slots - for (size_type i = exported.current_block_slot; i < this->num_items; ++i) - { - // Push the pointer to the free slots stack - free_slots.push(exported.memory_blocks.back() + i); - } - - // Always append the new memory blocks at the front so we don't change the back blocks - memory_blocks.insert(memory_blocks.begin(), exported.memory_blocks.begin(), - exported.memory_blocks.end()); - } - // We don't need to change the current_block_slot here - // As the imported allocator's partially free slots are already accounted for + // As the imported allocator's partially free slots are already accounted for during export + + // 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())); - // Clean up the imported structure to avoid potential reuse // Clear the imported memory blocks exported.memory_blocks = std::vector(); - // Reset the imported current block slot - exported.current_block_slot = 0; } template From 4f4426a2b1fd78a2eba3dc9b18370a91aabbf911 Mon Sep 17 00:00:00 2001 From: Canlin Zhang Date: Fri, 8 Aug 2025 12:57:49 -0700 Subject: [PATCH 12/15] Update pool_allocator.h to remove broken operators --- include/pool_allocator/pool_allocator.h | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/include/pool_allocator/pool_allocator.h b/include/pool_allocator/pool_allocator.h index fe0ce32..5971f97 100644 --- a/include/pool_allocator/pool_allocator.h +++ b/include/pool_allocator/pool_allocator.h @@ -190,14 +190,6 @@ class PoolAllocator }; // Operators -// Operator != and == -template -inline bool -operator==(const PoolAllocator&, const PoolAllocator&) noexcept -{ - return B1 == B2; -} - // Only two references to the same allocator are equal template inline bool @@ -206,13 +198,6 @@ operator==(const PoolAllocator& a, const PoolAllocator -inline bool -operator!=(const PoolAllocator& a, const PoolAllocator& b) noexcept -{ - return !(a == b); -} - template inline bool operator!=(const PoolAllocator& a, const PoolAllocator& b) noexcept From 7133329a6169d8ccb8aa222bc508d170bd1360a6 Mon Sep 17 00:00:00 2001 From: Canlin Zhang Date: Fri, 8 Aug 2025 20:42:46 +0000 Subject: [PATCH 13/15] Addressed other review issues --- include/pool_allocator/pool_allocator.h | 14 +++---- include/pool_allocator/pool_allocator.tcc | 48 ++++++----------------- 2 files changed, 18 insertions(+), 44 deletions(-) diff --git a/include/pool_allocator/pool_allocator.h b/include/pool_allocator/pool_allocator.h index 5971f97..8e55179 100644 --- a/include/pool_allocator/pool_allocator.h +++ b/include/pool_allocator/pool_allocator.h @@ -49,7 +49,7 @@ struct ExportedAlloc // Free slots in the block std::stack> free_slots; - // Memory blocks - Optional, only used in export_all and import_all + // Memory blocks - Optional, only used in _export_all and _import std::vector memory_blocks; }; @@ -159,19 +159,15 @@ class PoolAllocator //! 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(); + ExportedAlloc _export_free(); //! Export all the memory blocks + available slots - ExportedAlloc export_all(); + ExportedAlloc _export_all(); // Import - //! Import free slots from an ExportedAlloc - void import_free(ExportedAlloc& exported); - void import_free(PoolAllocator& from); - //! Import all memory blocks and free slots from an ExportedAlloc - void import_all(ExportedAlloc& exported); - void import_all(PoolAllocator& from); + void _import(ExportedAlloc& exported); + void _import(PoolAllocator& from); private: // Allocate a memory block diff --git a/include/pool_allocator/pool_allocator.tcc b/include/pool_allocator/pool_allocator.tcc index dcab5a0..1ea7d20 100644 --- a/include/pool_allocator/pool_allocator.tcc +++ b/include/pool_allocator/pool_allocator.tcc @@ -73,7 +73,7 @@ PoolAllocator::addressof(const_reference x) const noexcept template inline ExportedAlloc -PoolAllocator::export_free() +PoolAllocator::_export_free() { ExportedAlloc exported; exported.free_slots = std::move(free_slots); @@ -82,15 +82,13 @@ PoolAllocator::export_free() // No memory blocks to export exported.memory_blocks = std::vector(); - // No current block slot to export - exported.current_block_slot = 0; return exported; } template ExportedAlloc -PoolAllocator::export_all() +PoolAllocator::_export_all() { ExportedAlloc exported; // Before moving the free slots, unwind the partially free bump-allocation block @@ -104,8 +102,10 @@ PoolAllocator::export_all() exported.free_slots.push(memory_blocks.back() + i); } } - // Move the free slots to the exported struct - exported.free_slots = std::move(free_slots); + // Append existing free slots to the unwinded free slots + exported.free_slots.c.insert(exported.free_slots.c.end(), + std::make_move_iterator(free_slots.c.begin()), + std::make_move_iterator(free_slots.c.end())); // Clear the free slots stack free_slots = std::stack>(); @@ -122,33 +122,11 @@ PoolAllocator::export_all() template void -PoolAllocator::import_free(ExportedAlloc& exported) -{ - // Append the free slots from the exported allocator efficiently - free_slots.c.insert(free_slots.c.end(), std::make_move_iterator(exported.free_slots.c.begin()), - std::make_move_iterator(exported.free_slots.c.end())); - - // Clear the imported free slots to avoid potential reuse - exported.free_slots = std::stack>(); -} - -template -void -PoolAllocator::import_free(PoolAllocator& from) -{ - assert(&from != this && "Cannot import directly from self"); - - // Export and Import the free slots - auto exported = from.export_free(); - import_free(exported); -} - -template -void -PoolAllocator::import_all(ExportedAlloc& exported) +PoolAllocator::_import(ExportedAlloc& exported) { - // Import free slots - import_free(exported); + // Append the free slots from the exported allocator + free_slots.c.insert(free_slots.c.end(), std::make_move_iterator(exported.free_slots.begin()), + std::make_move_iterator(exported.free_slots.end())); // 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 @@ -164,13 +142,13 @@ PoolAllocator::import_all(ExportedAlloc& exported) template void -PoolAllocator::import_all(PoolAllocator& from) +PoolAllocator::_import(PoolAllocator& from) { assert(&from != this && "Cannot import directly from self"); // Export and Import the free slots - auto exported = from.export_all(); - import_all(exported); + auto exported = from._export_all(); + _import(exported); } template From 203d4ba997fe9f4693a3de6edfbace7f3e772124 Mon Sep 17 00:00:00 2001 From: Canlin Zhang Date: Fri, 8 Aug 2025 20:51:28 +0000 Subject: [PATCH 14/15] Remove unnecessary move iterators --- include/pool_allocator/pool_allocator.tcc | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/include/pool_allocator/pool_allocator.tcc b/include/pool_allocator/pool_allocator.tcc index 1ea7d20..5f9e3cb 100644 --- a/include/pool_allocator/pool_allocator.tcc +++ b/include/pool_allocator/pool_allocator.tcc @@ -103,9 +103,8 @@ PoolAllocator::_export_all() } } // Append existing free slots to the unwinded free slots - exported.free_slots.c.insert(exported.free_slots.c.end(), - std::make_move_iterator(free_slots.c.begin()), - std::make_move_iterator(free_slots.c.end())); + 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>(); @@ -125,8 +124,7 @@ void PoolAllocator::_import(ExportedAlloc& exported) { // Append the free slots from the exported allocator - free_slots.c.insert(free_slots.c.end(), std::make_move_iterator(exported.free_slots.begin()), - std::make_move_iterator(exported.free_slots.end())); + free_slots.c.insert(free_slots.c.end(), exported.free_slots.begin(), exported.free_slots.end()); // 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 From 998b576c21ff95a50d7b0e5f6fe37798d1d4ef66 Mon Sep 17 00:00:00 2001 From: Canlin Zhang Date: Fri, 8 Aug 2025 22:25:29 +0000 Subject: [PATCH 15/15] Added transfer_free and transfer_all functions --- include/pool_allocator/pool_allocator.h | 14 +++++++++----- include/pool_allocator/pool_allocator.tcc | 13 ++++++++++++- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/include/pool_allocator/pool_allocator.h b/include/pool_allocator/pool_allocator.h index 8e55179..c92e45f 100644 --- a/include/pool_allocator/pool_allocator.h +++ b/include/pool_allocator/pool_allocator.h @@ -153,6 +153,15 @@ class PoolAllocator 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. @@ -167,11 +176,6 @@ class PoolAllocator // Import //! Import all memory blocks and free slots from an ExportedAlloc void _import(ExportedAlloc& exported); - void _import(PoolAllocator& from); - - private: - // Allocate a memory block - void allocateBlock(); // Pointer to blocks of memory std::vector memory_blocks; diff --git a/include/pool_allocator/pool_allocator.tcc b/include/pool_allocator/pool_allocator.tcc index 5f9e3cb..ba9effe 100644 --- a/include/pool_allocator/pool_allocator.tcc +++ b/include/pool_allocator/pool_allocator.tcc @@ -140,7 +140,7 @@ PoolAllocator::_import(ExportedAlloc& exported) template void -PoolAllocator::_import(PoolAllocator& from) +PoolAllocator::transfer_all(PoolAllocator& from) { assert(&from != this && "Cannot import directly from self"); @@ -149,6 +149,17 @@ PoolAllocator::_import(PoolAllocator& from) _import(exported); } +template +void +PoolAllocator::transfer_free(PoolAllocator& from) +{ + 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()