Можно ли написать собственный распределитель STL, который использует указатели на функции распределения, предоставленные пользователем?

У нас есть библиотека, которая предоставляет интерфейс C через extern "C" и используется из кода C, но внутри она использует контейнеры STL и некоторые функции C++, такие как RAII, для удобства.

Теперь появилось новое требование, согласно которому библиотека должна иметь возможность принимать указатели на пользовательские функции malloc и free, поступающие из клиентского кода, и использовать их для внутренних распределений. Я могу поместить их в контекстную структуру библиотеки и использовать там, где это необходимо, но использование их с STL вызывает недоумение...

Я просмотрел классы распределителей, но кажется, что контейнеры STL должны иметь возможность использовать конструкторы по умолчанию для создания распределителя, и кажется, что нет способа поместить в них эти указатели, чтобы они могли вызывать через них для выполнения распределения.

Можно ли обойти это предпочтительно потокобезопасным способом (без использования глобальных переменных)?


person Calmarius    schedule 11.03.2015    source источник


Ответы (3)


Я просмотрел классы распределителя, но кажется, что контейнеры STL должны иметь возможность использовать конструкторы по умолчанию для создания распределителя.

Это неправда, все контейнеры могут быть созданы с помощью распределителя явно, поэтому вы можете создать свой объект распределителя, а затем передать его контейнеру.

extern "C"
{
  typedef void* (*allocation_function)(size_t);
  typedef void (*deallocation_function)(void*);
}

template<typename T>
class Allocator
{
public:
  typedef T value_type;

  Allocator(allocation_function alloc, deallocation_function dealloc)
  : m_allocate(alloc), m_deallocate(dealloc)
  { }

  template<typename U>
    Allocator(const Allocator<U>& a)
    : m_allocate(a.m_allocate), m_deallocate(a.m_deallocate)
    { }

  T* allocate(size_t n)
  { return static_cast<T*>(m_allocate(n * sizeof(T))); }

  void deallocate(T* p, size_t)
  { m_deallocate(p); }

private:
  template<typename U>
    friend class Allocator<U>;

  template<typename U>
    friend bool operator==(const Allocator<U>&, const Allocator<U>&);

  allocation_function   m_allocate;
  deallocation_function m_deallocate;
};

template<typename T>
bool operator==(const Allocator<T>& l, const Allocator<T>& r)
{ return l.m_allocate == r.m_allocate; }

template<typename T>
bool operator!=(const Allocator<T>& l, const Allocator<T>& r)
{ return !(l == r); }


Allocator<int> a(custom_malloc, custom_free);
std::vector<int, Allocator<int>> v(a);

Если вы еще не используете С++ 11, вам нужно предоставить гораздо больше элементов для вашего распределителя, чтобы соответствовать старым требованиям, но приведенный выше вариант подходит для С++ 11. Использование пользовательских распределителей в C++03 сложно и в любом случае непереносимо, поэтому вам следует стремиться использовать компилятор C++11, если вам нужно это сделать.

person Jonathan Wakely    schedule 11.03.2015
comment
Этот конструктор std::map вызывает у меня проблемы: map(): Mybase(key_compare(), allocator_type()) отсюда и вопрос. (В VS2008). Но это может быть хорошо, потому что есть экземпляры, созданные конструктором по умолчанию. Достаточно ли умны компиляторы, чтобы не жаловаться, если я вообще не использую конструктор по умолчанию при объявлении переменных? - person Calmarius; 11.03.2015
comment
да. Если вы не используете конструктор по умолчанию (и явно не создаете экземпляр всего типа map), то конструктор по умолчанию распределителя не нужен. - person Jonathan Wakely; 11.03.2015
comment
Я не знаю, сколько требований распределителя С++ 11 реализует VS2008, вам может потребоваться предоставить ряд других функций-членов, таких как construct и destroy, и определить несколько определений типов, таких как rebind<U>::other, pointer, const_pointer, reference и т. д., но если все это сделать, то должно получиться. - person Jonathan Wakely; 11.03.2015
comment
Действительно, после рефакторинга всех экземпляров для использования экземпляра распределителя он компилируется нормально. Это интересно, я никогда не знал, что компиляторы C++ такие умные. - person Calmarius; 11.03.2015
comment
Это потому, что std::map — это шаблон. Неиспользуемые функции-члены шаблонов классов не создаются. - person Jonathan Wakely; 11.03.2015

да. В качестве примера просмотрите заголовок gc/gc_allocator.h из сборщик мусора Boehm (вы можете легко заменить нижние вызовы GC_MALLOC и т. д. некоторыми указателями функций). См. этот ответ.

person Basile Starynkevitch    schedule 11.03.2015

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

person randomusername    schedule 11.03.2015
comment
Поскольку распределители не могут быть заполнены состоянием, но должны быть конструируемыми по умолчанию, ни одна из этих вещей неверна в наши дни. - person Jonathan Wakely; 11.03.2015
comment
@JonathanWakely Я говорю о C++-03 - person randomusername; 11.03.2015
comment
Они не обязаны быть конструируемыми по умолчанию даже в C++03. И неправда, что они не могут сохранять состояние, просто не гарантируется, что ваша реализация будет поддерживать это... но на практике все поддерживают распределители с состоянием. В любом случае, использование шаблонов не решает проблему использования пользовательских функций распределения (если только они не ссылаются только на глобальные переменные, что исключено ОП) - person Jonathan Wakely; 11.03.2015
comment
Проблема, которую я помню при создании распределителя с отслеживанием состояния, заключалась в перемещении ресурсов. Насколько я помню, GCC всегда копировал мой аллокатор. stackoverflow.com/a/24279177/1462718 Кажется, что распределители с отслеживанием состояния почти невозможны. - person Brandon; 11.03.2015