SeniorCodeOccasionalNot answered yet
Implement a fixed-size memory pool using placement new
Implement a fixed-size memory pool Pool<T, N> using placement new: it reserves storage for N objects up front and constructs/destroys them in place.
Constraints:
acquire(args...)constructs aTin a free slot and returns a pointer; throws when full.release(p)destroys the object explicitly and frees the slot — never calldeleteon it.- The backing storage must satisfy
T's size and alignment requirements. - No
new/deleteafter pool construction; destructor cleans up any unreturned objects.
#include <cstddef>
template<typename T, std::size_t N>
class Pool {
public:
template<typename... Args>
T* acquire(Args&&... args); // construct in a free slot
void release(T* p) noexcept; // destroy and free the slot
// your code here
};
Write the implementation.
Allocate a raw aligned buffer, use placement new to construct objects into it, call the destructor explicitly before reusing the slot, and never call delete on a placement-new pointer.
- ✗Calling delete on a pointer obtained from placement new — undefined behaviour
- ✗Not satisfying alignment requirements of the stored type
- ✗Forgetting to call the destructor explicitly before recycling a slot
- →How does std::allocator relate to placement new?
- →What is the purpose of std::launder and when do you need it?
Contents
Task
Implement a Pool<T, N> class that:
- Pre-allocates space for
Nobjects of typeTwithout constructing them - Provides
acquire()→ constructs aTinto the next free slot and returns a pointer - Provides
release(T*)→ explicitly destroys the object and marks the slot free - Does not call
newordeleteafter construction
Reference implementation
#include <array>
#include <bitset>
#include <cstddef>
#include <new>
#include <stdexcept>
#include <type_traits>
template<typename T, std::size_t N>
class Pool {
public:
Pool() noexcept = default;
Pool(const Pool&) = delete;
Pool& operator=(const Pool&) = delete;
~Pool() {
// Release any unreturned objects.
for (std::size_t i = 0; i < N; ++i) {
if (used_[i]) {
std::destroy_at(ptr_at(i));
}
}
}
template<typename... Args>
T* acquire(Args&&... args) {
for (std::size_t i = 0; i < N; ++i) {
if (!used_[i]) {
used_[i] = true;
return ::new (ptr_at(i)) T(std::forward<Args>(args)...);
}
}
throw std::bad_alloc{};
}
void release(T* p) noexcept {
for (std::size_t i = 0; i < N; ++i) {
if (used_[i] && ptr_at(i) == p) {
std::destroy_at(p);
used_[i] = false;
return;
}
}
}
private:
T* ptr_at(std::size_t i) noexcept {
return std::launder(reinterpret_cast<T*>(&storage_[i]));
}
std::array<std::aligned_storage_t<sizeof(T), alignof(T)>, N> storage_;
std::bitset<N> used_{};
};
Key points
std::aligned_storage_tgives a raw buffer with the correct size and alignment forT::new (ptr) T(...)constructsTat the given address without allocating memorystd::destroy_atcalls the destructor without freeing memorystd::laundertells the optimizer that this storage now contains a liveTobject
Usage
Pool<std::string, 4> pool;
auto* s1 = pool.acquire("hello");
auto* s2 = pool.acquire("world");
std::cout << *s1 << ' ' << *s2 << '\n'; // hello world
pool.release(s1);
auto* s3 = pool.acquire("reused slot");Contents