memory_traits спецификатор для allocator

Victor Gubin
Victor Gubin

В большинстве случаев создание не стандартной реализации шаблона allocator, связанно с необходимостью заменить вызовы к базовом операторам new и delete. Это необходимо для замены универсального алгоритма распределения памяти используемого стандартной библиотекой, и не подходящего для конкретного случая. При этом глобально переопределять std::new и std::delete нежелательно.

В тоже время, шаблон allocator весьма сложный класс, и все STL контейнеры требуют четкого соблюдения интерфейса Allocator от любой не стандартной реализации. Таким образом, для замены обращений к стандартному распределителю памяти, на более подходящий для конкретного контейнера, нужно посути продублировать код std::allocator в собственной реализации, изменив всего несколько строк кода.

Концепция memory_traits

memory_traits это небольшой proxy интерфейс над базовым распределителем памяти, который просто реализуем и может быть передан базовому шаблону allocator.

Например для  обращения к глобальным операторам new и delete memory_traits будет выглядеть следующим образом

struct memory_traits {

	static void* allocate(std::size_t count) {
		return ::operator new( count );
	}

	static void* allocate(std::size_t size, const std::nothrow_t&) noexcept
	{
		return ::operator new( size, std::nothrow );
	}

	static void release(void* const ptr) noexcept
	{
		::operator delete( ptr );
	}

};



Тогда в стандартной библиотеке можно ввести следующий шаблон

template<typename T, typename MemoryTraits >
class basic_allocator {
	typedef MemoryTraits memory_traits_type;
....
	pointer allocate(size_type __n, const void* = 0) {
		....
		pointer *ptr = memory_traits_type::allocate( _n );
		....
	}
...
	// rest 
};



И спецификацию std::allocator можно определить как  ( если это нужно ).

template<typename T>
class allocator: public basic_allocator <T, memory_traits> {
	typedef std::size_t size_type;
	typedef ptrdiff_t difference_type;
	typedef T* pointer;
	typedef const T* const_pointer;
	typedef T&  reference;
	typedef const T& const_reference;
	typedef T value_type;

	template<typename T1>
	struct rebind {
		typedef allocator<T1> other;
	};
	
	constexpr allocator() noexcept:
		basic_allocator<T, memory_traits>()
	{}

	constexpr allocator(const allocator& other) noexcept:
		heap_allocator_base<T, memory_traits>( other )
	{}

	template<typename _Tp1>
	constexpr allocator(const allocator<_Tp1>& other) noexcept
	{}

	~allocator() noexcept = default;
};



Таким образом, можно легко заменять std::allocator для любого контейнера, потребуется только реализовать memory_traits и передать его шаблону std::basic_allocator.

 Например:



Введение memory_traits и std::basic_allocator затронут уже существующий код. Но определить Allocator для конкретного контейнера станет намного проще.

-3
рейтинг
2 комментария
Victor Gubin
То что "вылетело" из секции например, и в чем был весь смысл:

#include <jemalloc/jemalloc.h>
#include <windows.h>

namespace jemalloc {

struct memory_traits {

static void* allocate(std::size_t size) {
void *ret = nullptr;
// some while(nullptr == __builtin_expect( (ret == std::malloc) ,false) ) {
while ( nullptr == (ret = je_malloc(size)) ) {
std::new_handler handler = std::get_new_handler();
if(nullptr == handler)
throw std::bad_alloc();
handler();
}
return ret;
}

static void* allocate(std::size_t size, const std::nothrow_t&) noexcept {
void *ret = nullptr;
// some while(nullptr == __builtin_expect( (ret == std::malloc) ,false) ) {
while ( nullptr == (ret = ::je_malloc(size)) ) {
std::new_handler handler = std::get_new_handler();
if(nullptr == handler)
return nullptr;
handler();
}
return ret;
}

static void release(void* const ptr) noexcept {
::je_free( ptr );
}

};


} // jemalloc

namespace windows {

class memory_traits {
public:
static void* allocate(std::size_t size) {
void *ret = nullptr;
// use some __assume
while ( nullptr == (ret = ::HeapAlloc( instance()->heap_, 0, size) ) ) {
std::new_handler handler = std::get_new_handler();
if(nullptr == handler)
throw std::bad_alloc();
handler();
}
}
static void* allocate(std::size_t size, const std::nothrow_t&) noexcept {
void *ret = nullptr;
// use some __assume
while ( nullptr == (ret = ::HeapAlloc( instance()->heap_, 0 , size ) ) ) {
std::new_handler handler = std::get_new_handler();
if(nullptr == handler) {
errno = ENOMEM;
return nullptr;
}
handler();
}
return ret;
}

static void release(void* const ptr) noexcept {
::HeapFree(instance()->heap_, 0, const_cast<LPVOID>(ptr) );
}
private:

static const memory_traits* instance() noexcept {
static memory_traits _instance( ::HeapCreate(0,0,0) );
return &_instance;
}

constexpr explicit memory_traits(::HANDLE heap) noexcept:
heap_(heap)
{}

::HANDLE heap_;
};

} // namesapce windows

...
void main() {

// std::allocator ( new/delete in most cases )
std::vector<int> v;

// red-black map with je_malloc nodes
std::set< int, std::less<int>, std::basic_allocator<int, jemalloc::memory_traits > > s;

// hash table with windows private heap allocator for buckets
typedef std::unordered_map<
int, std::string
std::hash<int>,
std::equal_to<int>,
std::basic_allocator< std::pair<int,std::string>, windows::memory_traits>
> int_hash_map;

}
Victor Gubin
Will Code For Food
Это же std::pmr::polymorphic_allocator и std::pmr::memory_resource, не?
Will Code For Food
Другие идеи
Группа создана, чтобы собирать предложения к стандарту C++, организовывать их внутренние обсуждения, помогать готовить их для отправки в комитет и защищать на общих собраниях в рабочей группе по С++ Международной организации по стандартизации (ISO).