2
\$\begingroup\$

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;
}
\$\endgroup\$
1
  • \$\begingroup\$ Just a quick comment: use std::byte instead of uint8_t. \$\endgroup\$
    – JDługosz
    Commented May 10, 2021 at 14:09

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.