I want to have dynamic continuous block of memory for different types, that inherit from single base class. I've written structure for that purpose. I know I play with a fire here, but I would like to know how dangerous it is to use this structure. Each element stored in DynamicPool must inherit from Base to support copying. I store additional vector of size_t , it stores offsets in memory of elements to support indexing.
This is header file:
class DynamicPool
{
public:
struct Base
{
virtual ~Base() = default;
virtual void OnCopy(uint8_t* buffer) = 0;
};
public:
DynamicPool(uint32_t capacity = 0);
DynamicPool(const DynamicPool& other);
DynamicPool(DynamicPool&& other) noexcept;
~DynamicPool();
template <typename T>
void Push(const T& elem)
{
static_assert(std::is_base_of<Base, T>::value, "BaseType is not base type of type T");
if (m_Size + sizeof(T) > m_Capacity)
reallocate(sizeof(T));
new(&m_Data[m_Size])T(elem);
m_Handles.push_back(m_Size);
m_Size += sizeof(T);
}
template <typename T, typename ...Args>
void Emplace(Args&& ...args)
{
static_assert(std::is_base_of<Base, T>::value, "BaseType is not base type of type T");
if (m_Size + sizeof(T) > m_Capacity)
reallocate(sizeof(T));
new(&m_Data[m_Size])T(std::forward<Args>(args)...);
m_Handles.push_back(m_Size);
m_Size += sizeof(T);
}
void Erase(size_t index);
template <typename T>
T& Get(size_t index)
{
static_assert(std::is_base_of<Base, T>::value, "BaseType is not base type of type T");
return *reinterpret_cast<T*>(&m_Data[m_Handles[index]]);
}
template <typename T>
const T& Get(size_t index) const
{
static_assert(std::is_base_of<Base, T>::value, "BaseType is not base type of type T");
return *reinterpret_cast<T*>(&m_Data[m_Handles[index]]);
}
Base& Back();
const Base& Back() const;
size_t Size() const { return m_Handles.size(); }
Base& operator[](size_t index);
const Base& operator[](size_t index) const;
private:
void destroy();
void reallocate(size_t minSize);
private:
size_t m_Size;
size_t m_Capacity;
uint8_t* m_Data;
std::vector<size_t> m_Handles;
static constexpr size_t sc_CapacityMultiplier = 2;
};
This is cpp:
DynamicPool::DynamicPool(uint32_t capacity)
:
m_Size(0),
m_Capacity(capacity)
{
m_Data = nullptr;
if (m_Capacity)
m_Data = new uint8_t[m_Capacity];
}
DynamicPool::DynamicPool(const DynamicPool& other)
:
m_Size(other.m_Size),
m_Capacity(other.m_Capacity)
{
destroy();
m_Handles = other.m_Handles;
m_Data = new uint8_t[m_Capacity];
for (size_t handle : m_Handles)
{
Base* base = reinterpret_cast<Base*>(&other.m_Data[m_Capacity]);
base->OnCopy(&m_Data[handle]);
}
}
DynamicPool::DynamicPool(DynamicPool&& other) noexcept
:
m_Size(other.m_Size),
m_Capacity(other.m_Capacity)
{
destroy();
m_Handles = std::move(other.m_Handles);
m_Data = other.m_Data;
other.m_Data = nullptr;
other.m_Size = 0;
other.m_Capacity = 0;
}
DynamicPool::~DynamicPool()
{
destroy();
}
void DynamicPool::Erase(size_t index)
{
size_t handle = m_Handles[index];
Base* base = reinterpret_cast<Base*>(&m_Data[handle]);
base->~Base();
if (index + 1 < m_Handles.size())
{
size_t elementSize = m_Handles[index + 1] - handle;
m_Size -= elementSize;
for (size_t i = index + 1; i < m_Handles.size(); ++i)
{
size_t nextHandle = m_Handles[i];
m_Handles[i] -= elementSize;
Base* next = reinterpret_cast<Base*>(&m_Data[nextHandle]);
next->OnCopy(&m_Data[handle]);
handle = nextHandle;
}
}
m_Handles.erase(m_Handles.begin() + index);
}
DynamicPool::Base& DynamicPool::Back()
{
return *reinterpret_cast<Base*>(&m_Data[m_Handles.back()]);
}
const DynamicPool::Base& DynamicPool::Back() const
{
return *reinterpret_cast<Base*>(&m_Data[m_Handles.back()]);
}
DynamicPool::Base& DynamicPool::operator[](size_t index)
{
return *reinterpret_cast<Base*>(&m_Data[m_Handles[index]]);
}
const DynamicPool::Base& DynamicPool::operator[](size_t index) const
{
return *reinterpret_cast<Base*>(&m_Data[m_Handles[index]]);
}
void DynamicPool::destroy()
{
if (m_Data)
{
for (size_t handle : m_Handles)
{
Base* base = reinterpret_cast<Base*>(&m_Data[handle]);
base->~Base();
}
delete[]m_Data;
}
}
void DynamicPool::reallocate(size_t minSize)
{
m_Capacity = minSize + (m_Capacity * sc_CapacityMultiplier);
uint8_t* tmp = new uint8_t[m_Capacity];
if (m_Data)
{
for (size_t handle : m_Handles)
{
Base* base = reinterpret_cast<Base*>(&m_Data[handle]);
base->OnCopy(&tmp[handle]);
}
delete[] m_Data;
}
m_Data = tmp;
}
std::byte
instead ofuint8_t
. \$\endgroup\$