首页 > 解决方案 > 什么数据类型用于自定义基于堆栈的分配器的缓冲区?

问题描述

我想创建自己的游戏引擎,所以我买了几本书,其中一本是 Jason Gregory 的Game Engine Architecture Second Edition,他建议实现一些自定义分配器。书中谈到的一种分配器是基于堆栈的分配器,但我在阅读时感到困惑。你如何在其中存储数据?你使用什么数据类型?例如,您是否使用, , 数组void*void**char[]? 这本书说你打算在开始时使用 malloc 分配一大块内存并在最后释放它,并通过递增指针来“分配”内存。如果您能帮助解释更多,那就太好了,因为我似乎找不到不使用 std::allocator 的教程。我还认为这可能会帮助其他对自定义分配器感兴趣的人,所以我在这里发布了这个问题。

这是他们在书中给出的头文件示例:

class StackAllocator
{
 public:
    // Represents the current top of the stack.
    // You can only roll back to the marker not to arbitrary locations within the stack
    typedef U32 Marker;

    explicit StackAllocator(U32 stackSize_bytes);

    void* alloc(U32 size_bytes); // Allocates a new block of the given size from stack top
    Marker getMarker(); // Returns a Marker to the current stack top
    void freeToMarker(Marker marker); // Rolls the stack back to a previous marker
    void clear(); // Clears the entire stack(rolls the stack back to zero)

private:
    // ...
}

编辑: 过了一段时间我得到了这个工作,但我不知道我是否做得对

头文件

typedef std::uint32_t U32;

struct Marker {
    size_t currentSize;
};

class StackAllocator
{
private:
    void* m_buffer; // Buffer of memory
    size_t m_currSize = 0;
    size_t m_maxSize;

public:
    void init(size_t stackSize_bytes); // allocates size of memory
    void shutDown();

    void* allocUnaligned(U32 size_bytes);

    Marker getMarker();
    void freeToMarker(Marker marker);

    void clear();
};

.cpp 文件

void StackAllocator::init(size_t stackSize_bytes) {
    this->m_buffer = malloc(stackSize_bytes);
    this->m_maxSize = stackSize_bytes;
}

void StackAllocator::shutDown() {
    this->clear();

    free(m_buffer);
    m_buffer = nullptr;
}

void* StackAllocator::allocUnaligned(U32 size_bytes) {
    assert(m_maxSize - m_currSize >= size_bytes);

    m_buffer = static_cast<char*>(m_buffer) + size_bytes;
    m_currSize += size_bytes;
    return m_buffer;
}

Marker StackAllocator::getMarker() {
    Marker marker;
    marker.currentSize = m_currSize;
    return marker;
}

void StackAllocator::freeToMarker(Marker marker) {
    U32 difference = m_currSize - marker.currentSize;
    m_currSize -= difference;
    m_buffer = static_cast<char*>(m_buffer) - difference;
}

void StackAllocator::clear() {
    m_buffer = static_cast<char*>(m_buffer) - m_currSize;
}

标签: c++memory-managementgame-engine

解决方案


好的,为简单起见,假设您正在跟踪MyFunClass引擎的集合。它可以是任何东西,并且您的线性分配器不一定要跟踪同质类型的对象,但通常就是这样做的。通常,在使用自定义分配器时,您会尝试“塑造”内存分配,以将静态数据与动态的、不常访问的数据与经常访问的数据分开,以优化您的工作集并实现引用的局部性。

鉴于您提供的代码,首先,您将分配内存池。为简单起见,假设您需要足够的空间来汇集 1000 个类型的对象MyFunClass

StackAllocator sa;
sa.Init( 1000 * sizeof(MyFunClass) );

然后每次你需要为 a “分配”一个新的内存块时FunClass,你可以这样做:

void* mem = sa.allocUnaligned( sizeof(MyFunClass) );

当然,这实际上并没有分配任何东西。所有的分配都已经在步骤 1 中发生了。它只是将一些已经分配的内存标记为正在使用。

它也没有构造一个MyFunClass. 你的分配器不是强类型的,所以它返回的内存可以解释为你想要的:作为字节流;作为 C++ 类对象的支持表示;等等

现在,您将如何使用以这种方式分配的缓冲区?一种常见的方法是放置新的:

auto myObj = new (mem) MyFunClass();

因此,现在您正在通过调用保留的内存空间中构造 C++ 对象allocUnaligned

(请注意,该allocUnaligned位让您了解为什么我们通常不编写自己的自定义分配器:因为它们很难做到正确!我们甚至还没有提到对齐问题。)

为了获得额外的荣誉,请查看将线性分配器方法提升到下一个级别的范围堆栈。


推荐阅读