如何显式管理异构类型(和大小)的多个池分配器?

aSt*_*eve 5 c++ boost memory-management c++11

场景:我有一个 G 类,它(通常)包含数千个从 N 类派生的类型的对象。所有这些对象都有一个明确定义的生命周期。首先,构造一个对象 G,然后添加 N 派生对象,然后用 G 完成一些计算,这不会改变 N 派生对象,然后 G 超出范围,并且随之而来的是, N 个派生对象。N 派生对象又包含指向添加到同一 G 对象的其他 N 派生对象的指针或指针的标准容器。G 表示具有异构节点的图。

我的目标是:

  1. 最小化分配每个 N 派生对象的成本。
  2. 最大化属于同一 G 对象的 N 派生对象的引用局部性。
  3. 通过为所有 N 派生对象解除分配单个块来最小化解除分配的成本。
  4. 能够定义多个具有独立生命周期的独立 G 对象 - 可能在并发线程中管理这些独立 G 对象,而无需线程同步。

对我来说,这似乎需要多个池分配器 - 就像使用堆栈一样进行分配......并且仅在池被销毁时释放池分配。

我查看了 boost 池分配器 - 但没有找到为不同大小的异构对象建立多个独立池的方法。

接下来,我定义了自己的自定义分配器 - 但很快发现,虽然我可以将其作为模板参数传递给标准容器,例如 std::vector、std::set、std::list 等。- 允许我指定池分配器的类型...我感到困惑,因为我无法轻松指定两个独立的容器应该共享相同的(非全局)分配器池。我认识到一种解决方案是使用静态/全局并限制自己仅在一个线程中构造 G 对象。我还考虑过使用线程本地存储将自定义分配器与相关池关联起来......但认为这很难看。这两种方法都不直接支持同一线程中两个独立 G 对象的交错构造。

我是否忽略了 Boost 中问题的现有解决方案?

有没有比使用静态/全局或线程本地存储更好的习惯来实现我的目标?

更新

我已经阅读了 Stroustrup 的常见问题解答以及 boost::container 文档。起初,我对 Boost::container 感到非常鼓舞 - 但很失望没有看到如何在这些容器中使用有状态分配器的具体示例。我已经能够简化我原来要问的问题......给定一个结构:

struct DataUnit { map<int,string> m; list<int> l; }
Run Code Online (Sandbox Code Playgroud)

如何确保对于 DataUnit 的每个实例,都有一个池来分配 m 和 l 的内部组成部分?如果我将自定义分配器传递给映射和列表,则 m 和 l 会获得此容器的独立实例。我最初以为我可以使用 get_allocator() 用我的 aerena/pool 初始化分配器...但是,遗憾的是,在 vector<...> 的默认构造函数返回之前调用 allocate() ..所以我不能那样做。

更奇怪的是,我发现,在涉足 boost::container 一段时间之后……普通的 std 容器有一个 get_allocator() (在 MSVC 2010 和 2012 以及 g++ 4.6.3 上),这表明这些标准库上下文具有与 boost::container 类似的有状态分配器支持。

不幸的是,我仍然没有可行的解决方案来解决我原来的问题(尽管我现在可以更雄辩地表达它。)

更新2

谢谢,pmr,你最后的评论是我授予“正确答案”的东西 - 如果你将其归类为答案。:) 我的问题是,找到 boost::container 后,我期望它的文档能够明确说明任何新功能 - 例如在构造时传递分配器对象...并且我没有检查 boost::container 源代码正确编码。我现在确信,Boost::container 为我上述所有问题提供了一个非常优雅且直观的(如果记录不完整)解决方案。再次感谢!

Soo*_*nts 1

警告:完全未经测试的代码。我不知道它是什么“惯用语” - 但下面的 1.5 页代码应该可以解决您的问题。

class GraphNodeAllocator
{
    struct CMemChunk
    {
        CMemChunk* pNext;
        BYTE* data()
        {
            return static_cast<BYTE*>( static_cast<void*>( this + 1 ) );
        }
    };

    CMemChunk* m_pChunk; // Most recently allocated a.k.a. current chunk
    BYTE* m_pFirstByte;  // First free data byte within the current chunk
    size_t m_freeBytes;  // Count of free bytes within the current chunk

    static const size_t cbChunkAlloc = 0x10000; // 65536 bytes per single allocation
    static const size_t cbChunkPayload = cbChunkAlloc - sizeof( CMemChunk );

    void* Allocate( size_t sz )
    {
        if( sz > cbChunkPayload )
            return NULL;

        if( m_freeBytes >= sz )
        {
            // Current chunk has the space
            m_freeBytes -= sz;
            void* res = m_pFirstByte;
            m_pFirstByte += sz;
            return res;
        }

        // Need a new chunk
        CMemChunk* pChunk = static_cast< CMemChunk* >( malloc( cbChunkAlloc ) );
        if( NULL == pChunk )
            return NULL;
        pChunk->pNext = m_pChunk;
        m_pChunk = pChunk;
        m_pFirstByte = m_pChunk->data();
        m_freeBytes = cbChunkPayload;
        return Allocate( sz );
    }

public:
    inline GraphNodeAllocator(): m_pChunk( NULL ), m_pFirstByte( NULL ), m_freeBytes( 0 ) { }

    inline ~GraphNodeAllocator()
    {
        while( NULL != m_pChunk )
        {
            CMemChunk* pNext;
            pNext = m_pChunk->pNext;
            free( m_pChunk );
            m_pChunk = pNext;
        }
    }

    template<typename E>
    inline E* newNode()
    {
        void* ptr = Allocate( sizeof( E ) );
        if( NULL == ptr ) return NULL;
        return ::new( ptr ) E();
    }
};
Run Code Online (Sandbox Code Playgroud)

PS 这个想法是从 Microsoft 的 CAtlPlex 类借用的,这是大多数 Microsoft 模板容器(列表、映射、哈希图)通常比 STL 对应项快 2 倍的首要原因。自从我放弃使用 std::vector、std::set、std::list 等而转而使用 ATL 的等价物后,我变得更加快乐。