Распределение памяти внутренних типов, используемых контейнерами

Стандарт C ++ 11 содержит следующие строки в Общих требованиях к контейнерам.

(23.2.1 — 3)

Для компонентов, затронутых в этом подпункте, которые объявляют тип allocator_type, объекты, хранящиеся в этих компонентах, должны создаваться с использованием функции allocator_traits :: construct и уничтожаться с использованием функции allocator_traits :: destroy (20.6.8.2). Эти функции вызываются только для типа элемента контейнера, не для внутренних типов, используемых контейнером

(23.2.1 — 7)

Если не указано иное, все контейнеры, определенные в этом пункте, получают память, используя распределитель

Это правда или нет, что все память, используемая контейнером, выделяется указанным распределителем? Поскольку стандарт говорит, что внутренние типы создаются не с помощью allocator_traits :: construct, поэтому должен быть какой-то вызов оператора new. Но стандарт также говорит, что все контейнеры, определенные в этом разделе, получают память с помощью распределителя, что, на мой взгляд, означает, что это не может быть обычный новый оператор, это должно быть размещение нового оператора. Я прав?

Позвольте мне показать вам пример, почему это важно.

Допустим, у нас есть класс, который содержит некоторую выделенную память:

#include <unordered_map>
#include <iostream>
#include <cstdint>
#include <limits>
#include <memory>
#include <new>

class Arena
{
public:
Arena(std::size_t size)
{
size_     = size;
location_ = 0;

data_ = nullptr;
if(size_ > 0)
data_ = new(std::nothrow) uint8_t[size_];
}
Arena(const Arena& other) = delete;
~Arena()
{
if(data_ != nullptr)
delete[] data_;
}
Arena& operator =(const Arena& arena) = delete;

uint8_t* allocate(std::size_t size)
{
if(data_ == nullptr)
throw std::bad_alloc();

if((location_ + size) >= size_)
throw std::bad_alloc();

uint8_t* result = &data_[location_];
location_ += size;
return result;
}

void clear()
{
location_ = 0;
}

std::size_t getNumBytesUsed() const
{
return location_;
}

private:
uint8_t* data_;
std::size_t location_, size_;

};

у нас также есть собственный распределитель:

template <class T> class FastAllocator
{
public:
typedef T value_type;

typedef T*       pointer;
typedef const T* const_pointer;

typedef T&       reference;
typedef const T& const_reference;

typedef std::size_t    size_type;
typedef std::ptrdiff_t difference_type;

template <class U> class rebind
{
public:
typedef FastAllocator<U> other;

};

Arena* arena;

FastAllocator(Arena& arena_): arena(&arena_) {}
FastAllocator(const FastAllocator& other): arena(other.arena) {}
template <class U> FastAllocator(const FastAllocator<U>& other): arena(other.arena) {}

//------------------------------------------------------------------------------------
pointer allocate(size_type n, std::allocator<void>::const_pointer)
{
return allocate(n);
}
pointer allocate(size_type n)
{
return reinterpret_cast<pointer>(arena->allocate(n * sizeof(T)));
}

//------------------------------------------------------------------------------------
void deallocate(pointer, size_type) {}

//------------------------------------------------------------------------------------
size_type max_size() const
{
return std::numeric_limits<size_type>::max();
}

//------------------------------------------------------------------------------------
void construct(pointer p, const_reference val)
{
::new(static_cast<void*>(p)) T(val);
}
template <class U> void destroy(U* p)
{
p->~U();
}

};

Вот как мы используем это:

typedef std::unordered_map<uint32_t, uint32_t, std::hash<uint32_t>, std::equal_to<uint32_t>,
FastAllocator<std::pair<uint32_t, uint32_t>>> FastUnorderedMap;

int main()
{
// Allocate memory in arena
Arena arena(1024 * 1024 * 50);
FastAllocator<uint32_t> allocator(arena);
FastAllocator<std::pair<uint32_t, uint32_t>> pairAllocator(arena);
FastAllocator<FastUnorderedMap> unorderedMapAllocator(arena);

FastUnorderedMap* fastUnorderedMap = nullptr;

try
{
// allocate memory for unordered map
fastUnorderedMap = unorderedMapAllocator.allocate(1);

// construct unordered map
fastUnorderedMap =
new(reinterpret_cast<void*>(fastUnorderedMap)) FastUnorderedMap
(
0,
std::hash<uint32_t>(),
std::equal_to<uint32_t>(),
pairAllocator
);

// insert something
for(uint32_t i = 0; i < 1000000; ++i)
fastUnorderedMap->insert(std::make_pair(i, i));
}
catch(std::bad_alloc badAlloc)
{
std::cout << "--- BAD ALLOC HAPPENED DURING FAST UNORDERED MAP INSERTION ---" << std::endl;
}

// no destructor of unordered map is called!!!!
return 0;
}

Как видите, деструктор unordered_map никогда не вызывается, но память освобождается при уничтожении объекта арены. Будет ли утечка памяти и почему?

Буду очень признателен за любую помощь по этой теме.

6

Решение

Предполагается, что распределитель обеспечивает 4 функции (представляющих интерес здесь):

  • 2 используются для управления памятью: allocate/deallocate
  • 2 используются для управления жизненным циклом объектов: construct/destroy

эти функции в вашей цитате относятся только к construct а также destroy (которые были упомянуты в предыдущем предложении), а не allocate/deallocateТаким образом, нет никакого противоречия.

Теперь, что касается утечек памяти, для работы распределителя арены должны работать не только объекты в контейнере с использованием распределителя арены (что гарантирует контейнер), но и вся память. эти объекты Выделение также должно быть получено от этого распределителя; это может быть немного сложнее, к сожалению.

8

Другие решения

Других решений пока нет …

По вопросам рекламы ammmcru@yandex.ru
Adblock
detector