C++11 内存池设计模式?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/16378306/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 20:18:44  来源:igfitidea点击:

C++11 memory pool design pattern?

c++c++11memory-pool

提问by Andrew Tomazos

I have a program that contains a processing phase that needs to use a bunch of different object instances (all allocated on the heap) from a tree of polymorphic types, all eventually derived from a common base class.

我有一个程序,它包含一个处理阶段,该阶段需要使用来自多态类型树的一堆不同对象实例(全部分配在堆上),所有这些实例最终都派生自一个公共基类。

As the instances may cyclically reference each other, and do not have a clear owner, I want allocated them with new, handle them with raw pointers, and leave them in memory for the phase (even if they become unreferenced), and then after the phase of the program that uses these instances, I want to delete them all at once.

由于实例可能会相互循环引用,并且没有明确的所有者,我想用 分配它们new,用原始指针处理它们,并将它们留在内存中用于阶段(即使它们变得未引用),然后在阶段之后在使用这些实例的程序中,我想一次删除它们。

How I thought to structure it is as follows:

我认为如何构建它如下:

struct B; // common base class

vector<unique_ptr<B>> memory_pool;

struct B
{
    B() { memory_pool.emplace_back(this); }

    virtual ~B() {}
};

struct D : B { ... }

int main()
{
    ...

    // phase begins
    D* p = new D(...);

    ...

    // phase ends
    memory_pool.clear();
    // all B instances are deleted, and pointers invalidated

    ...
}

Apart from being careful that all B instances are allocated with new, and that noone uses any pointers to them after the memory pool is cleared, are there problems with this implementation?

除了小心所有 B 实例都用 new 分配,并且在清除内存池后没有人使用任何指向它们的指针,这种实现是否有问题?

Specifically I am concerned about the fact that the thispointer is used to construct a std::unique_ptrin the base class constructor, before the derived class constructor has completed. Does this result in undefined behaviour? If so is there a workaround?

具体来说,我担心在派生类构造函数完成之前,this指针用于std::unique_ptr在基类构造函数中构造 a的事实。这会导致未定义的行为吗?如果是这样,是否有解决方法?

回答by TemplateRex

In case you haven't already, familiarize yourself with Boost.Pool. From the Boost documentation:

如果您还没有,请熟悉Boost.Pool。从 Boost 文档:

What is Pool?

Pool allocation is a memory allocation scheme that is very fast, but limited in its usage. For more information on pool allocation (also called simple segregated storage, see conceptsconcepts and Simple Segregated Storage.

Why should I use Pool?

Using Pools gives you more control over how memory is used in your program. For example, you could have a situation where you want to allocate a bunch of small objects at one point, and then reach a point in your program where none of them are needed any more. Using pool interfaces, you can choose to run their destructors or just drop them off into oblivion; the pool interface will guarantee that there are no system memory leaks.

When should I use Pool?

Pools are generally used when there is a lot of allocation and deallocation of small objects. Another common usage is the situation above, where many objects may be dropped out of memory.

In general, use Pools when you need a more efficient way to do unusual memory control.

Which pool allocator should I use?

pool_allocatoris a more general-purpose solution, geared towards efficiently servicing requests for any number of contiguous chunks.

fast_pool_allocatoris also a general-purpose solution but is geared towards efficiently servicing requests for one chunk at a time; it will work for contiguous chunks, but not as well as pool_allocator.

If you are seriously concerned about performance, use fast_pool_allocatorwhen dealing with containers such as std::list, and use pool_allocatorwhen dealing with containers such as std::vector.

什么是池?

池分配是一种内存分配方案,速度非常快,但使用受限。有关池分配(也称为简单隔离存储)的更多信息,请参阅概念概念和简单隔离存储

为什么要使用池?

使用 Pools 可以让您更好地控制程序中的内存使用方式。例如,您可能会遇到这样一种情况:您想在某个点分配一堆小对象,然后到达程序中不再需要它们的点。使用池接口,您可以选择运行它们的析构函数或将它们丢弃到遗忘中;池接口将保证没有系统内存泄漏。

我应该什么时候使用池?

池一般用于小对象大量分配和释放的时候。另一种常见的用法是上面的情况,其中许多对象可能会被丢弃在内存中。

通常,当您需要更有效的方法来进行异常内存控制时,请使用池。

我应该使用哪个池分配器?

pool_allocator是一种更通用的解决方案,旨在有效地为任意数量的连续块的请求提供服务。

fast_pool_allocator也是一种通用解决方案,但旨在一次有效地为一个块的请求提供服务;它适用于连续的块,但不如pool_allocator.

如果您非常关心性能,请 fast_pool_allocator在处理诸如 之类的容器时std::list使用,并pool_allocator在处理诸如 之类的容器时 使用std::vector

Memory management is tricky business (threading, caching, alignment, fragmentation, etc. etc.) For serious production code, well-designed and carefully optimized libraries are the way to go, unless your profiler demonstrates a bottleneck.

内存管理是一项棘手的业务(线程、缓存、对齐、碎片等)。对于严肃的生产代码,精心设计和精心优化的库是要走的路,除非您的分析器显示出瓶颈。

回答by TemplateRex

Your idea is great and millions of applications are already using it. This pattern is most famously known as ?autorelease pool?. It forms a base for ”smart” memory management in Cocoa and Cocoa Touch Objective-C frameworks. Despite the fact that C++ provides hell of a lot of other alternatives, I still think this idea got a lot of upside. But there are few things where I think your implementation as it stands may fall short.

你的想法很棒,数以百万计的应用程序已经在使用它。这种模式最著名的是“自动释放池”。它构成了 Cocoa 和 Cocoa Touch Objective-C 框架中“智能”内存管理的基础。尽管 C++ 提供了许多其他替代方案,但我仍然认为这个想法有很多好处。但是,我认为您的实施在某些方面可能会达不到要求。

The first problem that I can think of is thread safety. For example, what happens when objects of the same base are created from different threads? A solution might be to protect the pool access with mutually exclusive locks. Though I think a better way to do this is to make that pool a thread-specific object.

我能想到的第一个问题是线程安全。例如,当从不同线程创建相同基础的对象时会发生什么?一种解决方案可能是使用互斥锁保护池访问。虽然我认为更好的方法是使该池成为线程特定的对象。

The second problem is invoking an undefined behavior in case where derived class's constructor throws an exception. You see, if that happens, the derived object won't be constructed, but your B's constructor would have already pushed a pointer to thisto the vector. Later on, when the vector is cleared, it would try to call a destructor through a virtual table of the object that either doesn't exist or is in fact a different object (because newcould reuse that address).

第二个问题是在派生类的构造函数抛出异常的情况下调用未定义的行为。您会看到,如果发生这种情况,将不会构造派生对象,但您B的 构造函数已经将指针推this送到向量。稍后,当向量被清除时,它会尝试通过对象的虚拟表调用析构函数,该对象要么不存在,要么实际上是不同的对象(因为new可以重用该地址)。

The third thing I don't like is that you have only one global pool, even if it is thread-specific, that just doesn't allow for a more fine grained control over the scope of allocated objects.

我不喜欢的第三件事是你只有一个全局池,即使它是线程特定的,也不允许对分配对象的范围进行更细粒度的控制。

Taking the above into account, I would do a couple of improvements:

考虑到上述情况,我会做一些改进:

  1. Have a stack of pools for more fine-grained scope control.
  2. Make that pool stack a thread-specific object.
  3. In case of failures (like exception in derived class constructor), make sure the pool doesn't hold a dangling pointer.
  1. 拥有一堆池以进行更细粒度的范围控制。
  2. 使该池堆栈成为线程特定的对象。
  3. 如果出现故障(如派生类构造函数中的异常),请确保池不包含悬空指针。

Here is my literally 5 minutes solution, don't judge for quick and dirty:

这是我真正的 5 分钟解决方案,不要判断快速和肮脏:

#include <new>
#include <set>
#include <stack>
#include <cassert>
#include <memory>
#include <stdexcept>
#include <iostream>

#define thread_local __thread // Sorry, my compiler doesn't C++11 thread locals

struct AutoReleaseObject {
    AutoReleaseObject();
    virtual ~AutoReleaseObject();
};

class AutoReleasePool final {
  public:
    AutoReleasePool() {
        stack_.emplace(this);
    }

    ~AutoReleasePool() noexcept {
        std::set<AutoReleaseObject *> obj;
        obj.swap(objects_);
        for (auto *p : obj) {
            delete p;
        }
        stack_.pop();
    }

    static AutoReleasePool &instance() {
        assert(!stack_.empty());
        return *stack_.top();
    }

    void add(AutoReleaseObject *obj) {
        objects_.insert(obj);
    }

    void del(AutoReleaseObject *obj) {
        objects_.erase(obj);
    }

    AutoReleasePool(const AutoReleasePool &) = delete;
    AutoReleasePool &operator = (const AutoReleasePool &) = delete;

  private:
    // Hopefully, making this private won't allow users to create pool
    // not on stack that easily... But it won't make it impossible of course.
    void *operator new(size_t size) {
        return ::operator new(size);
    }

    std::set<AutoReleaseObject *> objects_;

    struct PrivateTraits {};

    AutoReleasePool(const PrivateTraits &) {
    }

    struct Stack final : std::stack<AutoReleasePool *> {
        Stack() {
            std::unique_ptr<AutoReleasePool> pool
                (new AutoReleasePool(PrivateTraits()));
            push(pool.get());
            pool.release();
        }

        ~Stack() {
            assert(!stack_.empty());
            delete stack_.top();
        }
    };

    static thread_local Stack stack_;
};

thread_local AutoReleasePool::Stack AutoReleasePool::stack_;

AutoReleaseObject::AutoReleaseObject()
{
    AutoReleasePool::instance().add(this);
}

AutoReleaseObject::~AutoReleaseObject()
{
    AutoReleasePool::instance().del(this);
}

// Some usage example...

struct MyObj : AutoReleaseObject {
    MyObj() {
        std::cout << "MyObj::MyObj(" << this << ")" << std::endl;
    }

    ~MyObj() override {
        std::cout << "MyObj::~MyObj(" << this << ")" << std::endl;
    }

    void bar() {
        std::cout << "MyObj::bar(" << this << ")" << std::endl;
    }
};

struct MyObjBad final : AutoReleaseObject {
    MyObjBad() {
        throw std::runtime_error("oops!");
    }

    ~MyObjBad() override {
    }
};

void bar()
{
    AutoReleasePool local_scope;
    for (int i = 0; i < 3; ++i) {
        auto o = new MyObj();
        o->bar();
    }
}

void foo()
{
    for (int i = 0; i < 2; ++i) {
        auto o = new MyObj();
        bar();
        o->bar();
    }
}

int main()
{
    std::cout << "main start..." << std::endl;
    foo();
    std::cout << "main end..." << std::endl;
}

回答by Cameron

Hmm, I needed almost exactly the same thing recently (memory pool for one phase of a program that gets cleared all at once), except that I had the additional design constraint that all my objects would be fairly small.

嗯,我最近需要几乎完全相同的东西(程序的一个阶段的内存池,一次全部清除),除了我有额外的设计约束,我的所有对象都相当小。

I came up with the following "small-object memory pool" -- perhaps it will be of use to you:

我想出了以下“小对象内存池”——也许它对你有用:

#pragma once

#include "defs.h"
#include <cstdint>      // uintptr_t
#include <cstdlib>      // std::malloc, std::size_t
#include <type_traits>  // std::alignment_of
#include <utility>      // std::forward
#include <algorithm>    // std::max
#include <cassert>      // assert


// Small-object allocator that uses a memory pool.
// Objects constructed in this arena *must not* have delete called on them.
// Allows all memory in the arena to be freed at once (destructors will
// be called).
// Usage:
//     SmallObjectArena arena;
//     Foo* foo = arena::create<Foo>();
//     arena.free();        // Calls ~Foo
class SmallObjectArena
{
private:
    typedef void (*Dtor)(void*);

    struct Record
    {
        Dtor dtor;
        short endOfPrevRecordOffset;    // Bytes between end of previous record and beginning of this one
        short objectOffset;             // From the end of the previous record
    };

    struct Block
    {
        size_t size;
        char* rawBlock;
        Block* prevBlock;
        char* startOfNextRecord;
    };

    template<typename T> static void DtorWrapper(void* obj) { static_cast<T*>(obj)->~T(); }

public:
    explicit SmallObjectArena(std::size_t initialPoolSize = 8192)
        : currentBlock(nullptr)
    {
        assert(initialPoolSize >= sizeof(Block) + std::alignment_of<Block>::value);
        assert(initialPoolSize >= 128);

        createNewBlock(initialPoolSize);
    }

    ~SmallObjectArena()
    {
        this->free();
        std::free(currentBlock->rawBlock);
    }

    template<typename T>
    inline T* create()
    {
        return new (alloc<T>()) T();
    }

    template<typename T, typename A1>
    inline T* create(A1&& a1)
    {
        return new (alloc<T>()) T(std::forward<A1>(a1));
    }

    template<typename T, typename A1, typename A2>
    inline T* create(A1&& a1, A2&& a2)
    {
        return new (alloc<T>()) T(std::forward<A1>(a1), std::forward<A2>(a2));
    }

    template<typename T, typename A1, typename A2, typename A3>
    inline T* create(A1&& a1, A2&& a2, A3&& a3)
    {
        return new (alloc<T>()) T(std::forward<A1>(a1), std::forward<A2>(a2), std::forward<A3>(a3));
    }

    // Calls the destructors of all currently allocated objects
    // then frees all allocated memory. Destructors are called in
    // the reverse order that the objects were constructed in.
    void free()
    {
        // Destroy all objects in arena, and free all blocks except
        // for the initial block.
        do {
            char* endOfRecord = currentBlock->startOfNextRecord;
            while (endOfRecord != reinterpret_cast<char*>(currentBlock) + sizeof(Block)) {
                auto startOfRecord = endOfRecord - sizeof(Record);
                auto record = reinterpret_cast<Record*>(startOfRecord);
                endOfRecord = startOfRecord - record->endOfPrevRecordOffset;
                record->dtor(endOfRecord + record->objectOffset);
            }

            if (currentBlock->prevBlock != nullptr) {
                auto memToFree = currentBlock->rawBlock;
                currentBlock = currentBlock->prevBlock;
                std::free(memToFree);
            }
        } while (currentBlock->prevBlock != nullptr);
        currentBlock->startOfNextRecord = reinterpret_cast<char*>(currentBlock) + sizeof(Block);
    }

private:
    template<typename T>
    static inline char* alignFor(char* ptr)
    {
        const size_t alignment = std::alignment_of<T>::value;
        return ptr + (alignment - (reinterpret_cast<uintptr_t>(ptr) % alignment)) % alignment;
    }

    template<typename T>
    T* alloc()
    {
        char* objectLocation = alignFor<T>(currentBlock->startOfNextRecord);
        char* nextRecordStart = alignFor<Record>(objectLocation + sizeof(T));
        if (nextRecordStart + sizeof(Record) > currentBlock->rawBlock + currentBlock->size) {
            createNewBlock(2 * std::max(currentBlock->size, sizeof(T) + sizeof(Record) + sizeof(Block) + 128));
            objectLocation = alignFor<T>(currentBlock->startOfNextRecord);
            nextRecordStart = alignFor<Record>(objectLocation + sizeof(T));
        }
        auto record = reinterpret_cast<Record*>(nextRecordStart);
        record->dtor = &DtorWrapper<T>;
        assert(objectLocation - currentBlock->startOfNextRecord < 32768);
        record->objectOffset = static_cast<short>(objectLocation - currentBlock->startOfNextRecord);
        assert(nextRecordStart - currentBlock->startOfNextRecord < 32768);
        record->endOfPrevRecordOffset = static_cast<short>(nextRecordStart - currentBlock->startOfNextRecord);
        currentBlock->startOfNextRecord = nextRecordStart + sizeof(Record);

        return reinterpret_cast<T*>(objectLocation);
    }

    void createNewBlock(size_t newBlockSize)
    {
        auto raw = static_cast<char*>(std::malloc(newBlockSize));
        auto blockStart = alignFor<Block>(raw);
        auto newBlock = reinterpret_cast<Block*>(blockStart);
        newBlock->rawBlock = raw;
        newBlock->prevBlock = currentBlock;
        newBlock->startOfNextRecord = blockStart + sizeof(Block);
        newBlock->size = newBlockSize;
        currentBlock = newBlock;
    }

private:
    Block* currentBlock;
};

To answer your question, you're not invoking undefined behaviour since nobody is using the pointer until the object is fully constructed (the pointer value itself is safe to copy around until then). However, it's a rather intrusive method, as the object(s) themselves need to know about the memory pool. Additionally, if you're constructing a large number of small objects, it would likely be faster to use an actual pool of memory (like my pool does) instead of calling out to newfor every object.

要回答您的问题,您不会调用未定义的行为,因为在对象完全构造之前没有人使用指针(指针值本身在此之前可以安全地复制)。然而,这是一种相当侵入性的方法,因为对象本身需要了解内存池。此外,如果您正在构建大量小对象,则使用实际的内存池(就像我的池一样)可能会更快,而不是new为每个对象调用。

Whatever pool-like approach you use, be careful that the objects are never manually deleteed, because that would lead to a double free!

无论您使用什么类似池的方法,请注意永远不要手动delete编辑对象,因为这会导致双重释放!

回答by Cameron

I still think this is an interesting question without a definitive reply, but please let me break it down into the different questions you are actually asking:

我仍然认为这是一个有趣的问题,没有明确的答复,但请让我将其分解为您实际提出的不同问题:

1.) Does inserting a pointer to a base class into a vector before initialisation of a subclass prevent or cause issues with retrieving inherited classes from that pointer. [slicing for example.]

1.) 在初始化子类之前将指向基类的指针插入向量是否会阻止或导致从该指针检索继承类的问题。[例如切片。]

Answer: No, so long as you are 100% sure of the relevant type that is being pointed to, this mechanism does not cause these issues however note the following points:

答:不会,只要您 100% 确定所指向的相关类型,此机制就不会导致这些问题,但请注意以下几点:

If the derived constructor fails, you are left with an issue later when you are likely to have a dangling pointer at least sitting in the vector, as that address space it [the derived class] thought it was getting would be freed to the operating environment on failure, but the vector still has the address as being of the base class type.

如果派生构造函数失败,那么当您可能至少在向量中有一个悬空指针时,您会遇到一个问题,因为它[派生类]认为它获得的地址空间将被释放到操作环境中失败时,但向量仍然具有基类类型的地址。

Note that a vector, although kind of useful, is not the best structure for this, and even if it was, there should be some inversion of control involved here to allow the vector object to control initialisation of your objects, so that you have awareness of success/failure.

请注意,矢量虽然有点用,但并不是最好的结构,即使是这样,这里也应该涉及一些控制反转,以允许矢量对象控制对象的初始化,以便您有意识成功/失败。

These points lead to the implied 2nd question:

这些点导致了隐含的第二个问题:

2.) Is this a good pattern for pooling?

2.) 这是一个很好的池化模式吗?

Answer: Not really, for the reasons mentioned above, plus others (Pushing a vector past it's end point basically ends up with a malloc which is unnecessary and will impact performance.) Ideally you want to use a pooling library, or a template class, and even better, separate the allocation/de-allocation policy implementation away from the pool implementation, with a low level solution already being hinted at, which is to allocate adequate pool memory from pool initialisation, and then use this using pointers to void from within the pool address space (See Alex Zywicki's solution above.) Using this pattern, the pool destruction is safe as the pool which will be contiguous memory can be destroyed en masse without any dangling issues, or memory leaks through losing all references to an object (losing all reference to an object whose address is allocated through the pool by the storage manager leaves you with dirty chunk/s, but will not cause a memory leak as it is managed by the pool implementation.

答:并非如此,由于上述原因,以及其他原因(将向量推过它的终点基本上最终会导致 malloc,这是不必要的并且会影响性​​能。)理想情况下,您希望使用池化库或模板类,更好的是,将分配/取消分配策略实现与池实现分开,已经暗示了一个低级解决方案,即从池初始化中分配足够的池内存,然后使用指向 void 的指针从内部使用它池地址空间(请参阅上面的 Alex Zywicki 的解决方案。)使用这种模式,池销毁是安全的,因为将是连续内存的池可以在没有任何悬空问题的情况下被集体销毁,或通过丢失对对象的所有引用而导致内存泄漏(丢失对存储管理器通过池分配地址的对象的所有引用会留下脏块/秒,但不会导致内存泄漏,因为它由池管理执行。

In the early days of C/C++ (before mass proliferation of the STL), this was a well discussed pattern and many implementations and designs can be found out there in good literature: As an example:

在 C/C++ 的早期(在 STL 大规模扩散之前),这是一个很好讨论的模式,并且可以在优秀的文献中找到许多实现和设计:例如:

Knuth (1973 The art of computer programming: Multiple volumes), and for a more complete list, with more on pooling, see:

Knuth(1973 年计算机编程的艺术:多卷),有关更完整的列表以及更多关于池化的内容,请参见:

http://www.ibm.com/developerworks/library/l-memory/

http://www.ibm.com/developerworks/library/l-memory/

The 3rd implied question seems to be:

第三个隐含的问题似乎是:

3) Is this a valid scenario to use pooling?

3)这是使用池化的有效场景吗?

Answer: This is a localised design decision based on what you are comfortable with, but to be honest, your implementation (no controlling structure/aggregate, possibly cyclic sharing of sub sets of objects) suggests to me that you would be better off with a basic linked list of wrapper objects, each of which contains a pointer to your superclass, used only for addressing purposes. Your cyclical structures are built on top of this, and you simply amend/grow shrink the list as required to accommodate all of your first class objects as required, and when finished, you can then easily destroy them in effectively an O(1) operation from within the linked list.

回答:这是基于您对什么感到满意的本地化设计决策,但老实说,您的实现(没有控制结构/聚合,可能循环共享对象的子集)向我建议您最好使用包装对象的基本链接列表,每个对象都包含一个指向您的超类的指针,仅用于寻址目的。您的循环结构建立在此之上,您只需根据需要修改/扩大缩小列表以根据需要容纳所有第一类对象,完成后,您可以轻松地以 O(1) 操作有效地销毁它们从链表中。

Having said that, I would personally recommend that at this time (when you have a scenario where pooling does have a use and so you are in the right mind-set) to carry out the building of a storage management/pooling set of classes that are paramaterised/typeless now as it will hold you in good stead for the future.

话虽如此,我个人建议此时(当您有一个池确实有用的场景,因此您处于正确的心态时)执行存储管理/池化类集的构建现在是参数化/无类型的,因为它会让你对未来有好处。

回答by Alex Zywicki

This sounds what I have heard called a Linear Allocator. I will explain the basics of how I understand how it works.

这听起来就是我听说的线性分配器。我将解释我如何理解它是如何工作的基础知识。

  1. Allocate a block of memory using ::operator new(size);
  2. Have a void* that is your Pointer to the next free space in memory.
  3. You will have an alloc(size_t size) function that will give you a pointer to the location in the block from step one for you to construct on to using Placement New
  4. Placement new looks like... int* i = new(location)int(); where location is a void* to a block of memory you alloced from the allocator.
  5. when you are done with all of your memory you will call a Flush() function that will dealloc the memory from the pool or at least wipe the data clean.
  1. 使用 ::operator new(size); 分配一块内存;
  2. 有一个 void* ,它是您指向内存中下一个可用空间的指针。
  3. 您将拥有一个 alloc(size_t size) 函数,该函数将为您提供一个指向块中从第一步开始的位置的指针,以便您构建到使用 Placement New
  4. 放置新看起来像... int* i = new(location)int(); 其中 location 是您从分配器分配的内存块的 void* 。
  5. 当您完成所有内存时,您将调用 Flush() 函数,该函数将从池中释放内存或至少清除数据。

I programmed one of these recently and i will post my code here for you as well as do my best to explain.

我最近编写了其中一个程序,我会在这里为您发布我的代码,并尽我所能解释一下。

    #include <iostream>
    class LinearAllocator:public ObjectBase
    {
    public:
        LinearAllocator();
        LinearAllocator(Pool* pool,size_t size);
        ~LinearAllocator();
        void* Alloc(Size_t size);
        void Flush();
    private:
        void** m_pBlock;
        void* m_pHeadFree;
        void* m_pEnd;
    };

don't worry about what i'm inheriting from. i have been using this allocator in conjunction with a memory pool. but basically instead of getting the memory from operator new i am getting memory from a memory pool. the internal workings are the same essentially.

不要担心我继承了什么。我一直在将此分配器与内存池结合使用。但基本上不是从 operator new 获取内存,而是从内存池中获取内存。内部工作原理基本相同。

Here is the implementation:

这是实现:

LinearAllocator::LinearAllocator():ObjectBase::ObjectBase()
{
    m_pBlock = nullptr;
    m_pHeadFree = nullptr;
    m_pEnd=nullptr;
}

LinearAllocator::LinearAllocator(Pool* pool,size_t size):ObjectBase::ObjectBase(pool)
{
    if (pool!=nullptr) {
        m_pBlock = ObjectBase::AllocFromPool(size);
        m_pHeadFree = * m_pBlock;
        m_pEnd = (void*)((unsigned char*)*m_pBlock+size);
    }
    else{
        m_pBlock = nullptr;
        m_pHeadFree = nullptr;
        m_pEnd=nullptr;
    }
}
LinearAllocator::~LinearAllocator()
{
    if (m_pBlock!=nullptr) {
        ObjectBase::FreeFromPool(m_pBlock);
    }
    m_pBlock = nullptr;
    m_pHeadFree = nullptr;
    m_pEnd=nullptr;
}
MemoryBlock* LinearAllocator::Alloc(size_t size)
{
    if (m_pBlock!=nullptr) {
        void* test = (void*)((unsigned char*)m_pEnd-size);
        if (m_pHeadFree<=test) {
            void* temp = m_pHeadFree;
            m_pHeadFree=(void*)((unsigned char*)m_pHeadFree+size);
            return temp;
        }else{
            return nullptr;
        }
    }else return nullptr;
}
void LinearAllocator::Flush()
{
    if (m_pBlock!=nullptr) {
        m_pHeadFree=m_pBlock;
        size_t size = (unsigned char*)m_pEnd-(unsigned char*)*m_pBlock;
        memset(*m_pBlock,0,size);
    }
}

This code is fully functional except for a few lines which will need to be changed because of my inheritance and use of the memory pool. but I bet you can figure out what needs to change and just let me know if you need a hand changing the code. This code has not been tested in any sort of professional manor and is not guaranteed to be thread safe or anything fancy like that. i just whipped it up and thought i could share it with you since you seemed to need help.

除了由于继承和使用内存池而需要更改的几行之外,此代码功能齐全。但我敢打赌,您可以找出需要更改的内容,如果您需要更改代码,请告诉我。这段代码没有经过任何专业庄园的测试,也不能保证线程安全或任何类似的东西。我只是想起来了,我想我可以和你分享,因为你似乎需要帮助。

I also have a working implementation of a fully generic memory pool if you think it may help you. I can explain how it works if you need.

如果您认为它可以帮助您,我还有一个完全通用的内存池的工作实现。如果您需要,我可以解释它是如何工作的。

Once again if you need any help let me know. Good luck.

如果您需要任何帮助,请再次告诉我。祝你好运。