6
\$\begingroup\$

This is a fixed-size memory allocator.
The core memory block is just a unsigned char pointer which gets allocated on initialization. Other than that has pretty basic allocation and block safety checking functionality.

Would appreciate any feedback.

template <size_t block_size, size_t blocks>
class MemoryAllocator
{
    // internal memory type
    using uchar = unsigned char;

    // byte patterns to mark memory blocks
    enum Pattern : uchar
    {
        UNALLOCATED = 0xAA,
        ALLOCATED = 0xBB
    };

    // meta data
    static constexpr size_t pad_bytes = 3;
    static constexpr size_t hb_size = pad_bytes + block_size;
    static constexpr size_t bytes_allocated = hb_size * blocks;

    // internal memory block
    uchar* data_;
    size_t open_blocks_;

    public:

        /**
         * @brief Construct a new Memory Allocator object.
         * 
         */
        MemoryAllocator() : open_blocks_(blocks)
        {
            data_ = new uchar[bytes_allocated];

            std::memset(data_, 0, bytes_allocated);

            // set header blocks to free
            for (size_t i = 0; i < blocks; ++i)
                std::memset(data_ + (i * hb_size), Pattern::UNALLOCATED, pad_bytes);
        }

        /**
         * @brief Destructor
         * 
         */
        ~MemoryAllocator() noexcept
        {
            delete [] data_;
        }

        /**
         * @brief Allocates memory.
         * 
         * @return void* 
         */
        template <typename T, typename... Args>
        T* Allocate(Args&&... args) noexcept
        {
            if constexpr (!std::is_same<T, void>::value)
                static_assert((alignof(T) + sizeof(T)) <= block_size, "Block cannot hold T.");

            if (open_blocks_ == 0)
                return nullptr;

            for (size_t i = 0; i < blocks; ++i)
            {
                size_t offset = i * hb_size;

                // safety check
                bool valid = true;

                for (size_t j = 0; j < pad_bytes; ++j)
                {
                    if (data_[offset + j] != Pattern::UNALLOCATED)
                    {
                        valid = false;
                        break;
                    }
                }

                if (!valid) continue;

                std::memset(data_ + offset, Pattern::ALLOCATED, pad_bytes);

                --open_blocks_;

                if constexpr (std::is_same<T, void>::value)
                {
                    return (data_ + offset + pad_bytes);
                }
                else
                {
                    return new(data_ + offset + pad_bytes) T(args...);
                }
            }

            return nullptr;
        }

        /**
         * @brief Frees memory.
         * 
         * @param block 
         */
        void Free(void* block)
        {
            uchar* mem = reinterpret_cast<uchar*>(block) - pad_bytes;

            // safety check
            for (size_t i = 0; i < pad_bytes; ++i)
                if (mem[i] != Pattern::ALLOCATED)
                    throw std::runtime_error("Freeing corrupted block!");

            std::memset(mem, Pattern::UNALLOCATED, pad_bytes);

            ++open_blocks_;

            if (open_blocks_ > blocks)
                throw std::runtime_error("Too many blocks freed");
        }

        /**
         * @brief Whether or not there is room for more allocations.
         * 
         * @return true 
         * @return false 
         */
        bool CanAllocate() const noexcept
        {
            return open_blocks_ > 0;
        }

        // prevent copying of any kind
        MemoryAllocator& operator=(MemoryAllocator& rhs) = delete;
        MemoryAllocator(const MemoryAllocator& rhs) = delete;
        MemoryAllocator(MemoryAllocator&& rhs) = delete;
};

/**
 * @brief Works with the MemoryAllocator to allocate for classes.
 * 
 * @tparam T 
 * @tparam blocks 
 */
template <typename T, size_t blocks>
struct ClassAllocator : public MemoryAllocator<sizeof(T) + alignof(T), blocks>
{
    template <typename... Args>
    void* Allocate(Args&&... args) noexcept
    {
        return MemoryAllocator<sizeof(T) + alignof(T), blocks>::template Allocate<void>(std::forward<Args>(args)...);
    }
};
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.