Реализация STL, которая использует динамический / основанный на состоянии распределитель? - PullRequest
6 голосов
/ 04 февраля 2011

Кто-нибудь знает о реализации STL, которая позволяет динамическим распределителям передаваться в экземпляр контейнера перед использованием.

Сценарий состоит в том, что у нас есть общий распределитель памяти, который управляет несколькими пуламипамяти и для каждого экземпляра скажем stl :: vector мы хотим выделить каждый экземпляр из другого пула памяти.

Проблема со стандартными реализациями STL состоит в том, что вы можете определять пул памяти только на основе типа, то есть все векторы типа int будут выделяться из одного и того же пула.

Я уже поменял местаминаш stl :: allocator по умолчанию для того, у которого есть состояние, то есть пул, из которого мы хотим выделить этот экземпляр, но это не очень хорошо работает, скажем, для stl :: list, где он размещает вещи в ctor по умолчанию.

По причинам, связанным с нашей библиотекой, у нас также нет действительного пула в ctor для всех объектов, поэтому мы хотим вызвать функцию 'set memory pool', прежде чем пользователи смогут использовать контейнер stl.

Кто-нибудь сталкивался с реализацией, которая поддерживает подобные вещи?

Ответы [ 5 ]

4 голосов
/ 04 февраля 2011

Я не совсем уверен в вашем вопросе точно.Поэтому я расскажу о случае распределителя с полным состоянием.

В C ++ 03 любой распределитель должен иметь возможность освобождать ресурсы, выделенные другим распределителем того же типа.

Стандарт C ++ 0x фактически снял это ограничение и позволяет распределителям полного состояния передаваться в контейнеры STL, если они являются Allocator Aware контейнерами (я думаю, что он охватывает все контейнеры, упакованные в STL, поскольку они моделируют Sequence ).

Например: [allocator.adaptor] $20.10 Class scoped_allocator теперь является частью C ++ 0x STL.

2 голосов
/ 04 февраля 2011

Типизированный распределитель может использовать общий распределитель внизу для выполнения распределений.

Распределитель должен поддерживать следующие функции:

  pointer address ( reference x ) const;
  const_pointer address ( const_reference x ) const;
  pointer allocate (size_type n, allocator<void>::const_pointer hint=0);
  void deallocate (pointer p, size_type n);
  size_type max_size() const throw();
  void construct ( pointer p, const_reference val );

Предполагая, что у вас есть механизм, который просто выделяет память и освобождает память, вы можете использовать его для реализации некоторых из перечисленных выше функций.

Преимущество вводимых распределителей состоит в том, что вы знаете, что собираетесь создавать множество элементов одинакового размера и, следовательно, можете создавать свои "страницы", чтобы соответствовать размеру. Самая большая проблема может заключаться в том, что вы заставляете allocate () возвращать смежные буферы (и они действительно нужны в векторе).

http://www.cplusplus.com/reference/std/memory/allocator/

Ваш вопрос все еще немного неясен относительно того, почему этого недостаточно. Вы можете инициализировать пул памяти логикой «один раз». (Если он многопоточный, вы можете использовать boost :: Once для достижения этой цели).

1 голос
/ 04 февраля 2011

Проблема со стандартом STL реализации, что вы можете только определить пул памяти на основе типа то есть все векторы типа int будут выделить из того же пула.

Это не совсем так. Вы можете иметь разные векторы, содержащие int элементов, каждый из которых имеет свой тип распределителя.

Однако по вопросу -

Кто-нибудь знает о STL реализация, которая позволяет динамически распределители должны быть переданы в экземпляр контейнера перед использованием.

- он просто не поддерживается стандартной библиотекой C ++ (STL), поэтому, хотя могут быть реализации, в которых работают распределители для отдельных объектов, он не переносим.

Подробный анализ того, почему и как использовать пользовательские распределители, см. Книга Скотта Мейерса Эффективный STL , в частности Пункт 11: Понимание законного использования пользовательских распределителей .

0 голосов
/ 05 февраля 2011

Хорошо, поэтому порт STL, по-видимому, поддерживает этот вид функциональности, в отличие от Microsoft (VS 2008) и реализации GNU (порт stl около gcc 3.4.1), поскольку они распределяют / освобождают объекты в ctors/dtors.

Вот мой тестовый код, показывающий, как это сделать.Предупреждение, что это не полная реализация любым способом!

#include <list>
#include <assert.h>

namespace my
{

class memory_pool
{
public:
    memory_pool() : m_top( 0 ){};
    ~memory_pool(){};

    void* alloc( int size, int align ) { void* p = (void*)&m_pool[m_top]; m_top += size; return p; }
    void free ( void* p ) { assert( (p >= m_pool) && (p < &m_pool[m_top] ) ); }
private:
    char m_pool[0xFFFF];
    int m_top;
};

template<class T>
class dynamic_allocator 
{
    template<typename U> friend class dynamic_allocator;
public:
    typedef T                   value_type;         
    typedef size_t              size_type;          
    typedef value_type*         pointer;            
    template <class _Tp1> struct rebind { typedef dynamic_allocator<_Tp1> other; };

    dynamic_allocator() : m_pPool( NULL ){}
    dynamic_allocator( memory_pool* pPool ){ m_pPool = pPool; }
    dynamic_allocator( const dynamic_allocator< T >& alloc ) : m_pPool( alloc.m_pPool ){}
    template< typename U >
    dynamic_allocator( const dynamic_allocator< U >& alloc ) : m_pPool( alloc.m_pPool ){}
    ~dynamic_allocator() {}

    pointer allocate( size_type count ){ return allocate( count, NULL ); }
    pointer allocate( size_type count, const void* ) { assert( m_pPool ); return ( pointer )m_pPool->alloc( count * sizeof( T ), __alignof( T ) ); }
    void deallocate( pointer p, size_type count )   { assert( m_pPool ); m_pPool->free( p ); }

    void set( memory_pool* pPool ) { m_pPool = pPool; }

private:
    memory_pool* m_pPool;
};

template< typename T, typename Al = dynamic_allocator<T>  >
class list : public std::list<T, Al>
{
public:
    typedef typename std::list<T, Al>::allocator_type allocator_type;

    list() : std::list<T, Al>(){};
    list( const allocator_type& a ) : std::list<T, Al>( a ){};
    ~list(){};

    void initialise( memory_pool& pool ){ std::list<T, Al>::_M_node.set( &pool ); } // or something like this
    void terminate( void ){ clear(); std::list<T, Al>::_M_node.set( NULL ); }                   // or something like this
};

}; // namespace my

class lemon
{
public:
    lemon(){}       // must be empty ctor as we don't want to have active mem pool in ctor for users to use
    ~lemon(){}

    void initialise( my::memory_pool& pool ){ m_list.initialise( pool ); }
    void terminate( void )                  { m_list.terminate(); }

    void add( float f ) { m_list.push_back( f ); }

private:
    my::list<float> m_list;
};

int main( void )
{
    my::memory_pool poolA;
    my::memory_pool poolB;

    my::dynamic_allocator<float> aa( &poolA );
    my::list<float> a( aa );
    my::list<float> fail;

    std::list<float>::allocator_type bb;
    std::list<float> b( bb );

    a.push_back( 0.2f );
    b.push_back( 50.0f );
    //fail.push_back( 19.0f );

    a.clear();
    b.clear();

    lemon lemons[2];

    lemons[0].initialise( poolA );
    lemons[1].initialise( poolB );

    lemons[0].add( 10.0f );
    lemons[1].add( 20.0f );
    lemons[1].add( 18.0f );

    lemons[0].terminate();
    lemons[1].terminate();

    scanf("press any key\n");

    return 0;
}
0 голосов
/ 04 февраля 2011

Один из вариантов - использовать локальную переменную потока для хранения указателя на пул памяти и использовать его в реализации вашего распределителя.Например (используя boost :: thread_specific_ptr ):

// Global variable
boost::thread_specific_ptr<allocator_impl> real_allocator;

struct set_allocator : boost::noncopyable {
private:
    allocator_impl *old;
public:
    set_allocator(allocator_impl *newAllocator) {
        old = real_allocator.get();
        real_allocator.reset(newAllocator);
    }
    ~set_allocator() {
        real_allocator.reset(old);
    }
};

template<typename T>
struct myallocator {
private:
    real_allocator *delegate;
public:
    myallocator() {
        delegate = real_allocator.get();
    }
    T *allocate(size_t n,  allocator<void>::const_pointer hint=0)
    {
        return delegate->allocate(sizeof(T), n, hint);
    }
    // Other mandatory STL Allocator functions and typedefs follow
};

// later:
allocator_impl *allocator = new allocator_impl();
set_allocator sa(allocator); // Set current allocator using RAII
std::list<int, myallocator> mylist; // using *allocator as the backing allocator

API, который должен реализовывать myallocator, описан здесь .

К сожалению, из-зак ограничениям в STL API, это так же хорошо, как вы можете получить без переопределения STL.Однако могут быть сторонние библиотеки, которые позволяют вам передавать распределитель в конструктор объекта где-то там.

...