SeniorКодИногдаЕщё не отвечали
Реализуйте пул памяти фиксированного размера с использованием placement new
Реализуйте пул памяти фиксированного размера Pool<T, N> на placement new: он заранее резервирует место под N объектов и конструирует/разрушает их на месте.
Ограничения:
acquire(args...)конструируетTв свободном слоте и возвращает указатель; бросает при заполнении.release(p)явно разрушает объект и освобождает слот — никогда не вызывайтеdeleteна нём.- Буфер должен удовлетворять требованиям размера и выравнивания
T. - Никаких
new/deleteпосле конструирования пула; деструктор убирает не возвращённые объекты.
#include <cstddef>
template<typename T, std::size_t N>
class Pool {
public:
template<typename... Args>
T* acquire(Args&&... args); // сконструировать в свободном слоте
void release(T* p) noexcept; // разрушить и освободить слот
// ваш код здесь
};
Допишите реализацию.
Выделите сырой выровненный буфер, используйте placement new для конструирования объектов в нём, явно вызывайте деструктор перед повторным использованием слота и никогда не вызывайте delete для указателя, полученного через placement new.
- ✗Вызов delete для указателя, полученного через placement new — неопределённое поведение
- ✗Несоблюдение требований выравнивания для хранимого типа
- ✗Забытый явный вызов деструктора перед повторным использованием слота
- →Как std::allocator связан с placement new?
- →Для чего нужен std::launder и когда он требуется?
Оглавление
Задача
Реализуйте класс Pool<T, N>, который:
- Предварительно резервирует место под
Nобъектов типаT, не конструируя их - Предоставляет
acquire()→ конструируетTв следующем свободном слоте и возвращает указатель - Предоставляет
release(T*)→ явно разрушает объект и помечает слот как свободный - Не вызывает
newилиdeleteпосле инициализации пула
Эталонная реализация
#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() {
// Освобождаем все объекты, которые не были возвращены в пул.
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_{};
};
Ключевые моменты
std::aligned_storage_tдаёт сырой буфер с корректными размером и выравниванием дляT::new (ptr) T(...)конструируетTпо указанному адресу без выделения новой памятиstd::destroy_atвызывает деструктор, не освобождая памятьstd::launderнужен, чтобы сообщить компилятору: в этом участке хранения уже живой объектTи к нему применяются правила aliasing
Использование
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");Оглавление