На этом шаге мы рассмотрим использование распределителей при разработке библиотек.
Этот шаг посвящен использованию распределителей с точки зрения людей, занимающихся реализацией контейнеров и других компонентов, способных работать с разными распределителями.
Распределители предоставляют интерфейс для выделения и освобождения памяти, создания и уничтожения объектов (таблица 1).
Выражение | Описание |
---|---|
a.allocate(num) | Выделение памяти для num элементов |
a.construct(p) | Инициализация элемента, на который ссылается указатель р |
a.destroy(p) | Уничтожение элемента, на который ссылается указатель р |
a.deallocate(p, num) | Освобождение памяти для num элементов, на которую ссылается указатель р |
Распределители делают возможной параметризацию контейнеров и алгоритмов по способу хранения элементов. Например, они могут работать с общей памятью или отображать элементы на записи базы данных.
В качестве примера рассмотрим упрощенную реализацию вектора. Вектор получает распределитель памяти в виде аргумента шаблона или конструктора и сохраняет ее в своем внутреннем представлении:
namespace std { template <class T, class Allocator = allocator<T> > class vector { ... private: Allocator alloc; // Распределитель T* elems; // Массив элементов size_type numElems; // Количество элементов size_type sizeElems; // Объем памяти для хранения элементов ... public: // Конструкторы explicit vector(const Allocator&=Allocator()); explicit vector(size_type num, const T& val=T(), const Allocator& = Allocator()); template <class InputIterator> vector(InputIterator beg, InputIterator end, const Allocator& = Allocator()); vector(const vector<T,Allocator>& v); }; }
Возможная реализация второго конструктора, инициализирующего вектор num элементами со значением val, может выглядеть так:
namespace std { template <class T, class Allocator> vector<T,Allocator>::vector(size_type num, const T& val, const Allocator& a) : alloc(a) // Инициализация распределителя { // Выделение памяти sizeElems = numElems = num; elems = alloc.allocate(num); // Инициализация элементов for (size_type i=0; i<num; ++i) { // Инициализация i-го элемента alloc.construct(&elems[i],val); } } }
В стандартную библиотеку C++ включены также вспомогательные функции для инициализации неинициализированной памяти (таблица 2). При использовании этих функций реализация конструктора выглядит еще проще:
namespace std { template <class T, class Allocator> vector<T,Allocator>::vector(size_type num, const T& val, const Allocator& a) : alloc(a) // Инициализация распределителя { // Выделение памяти sizeElems = numElems = num; elems = alloc.allocate(num); // Инициализация элементов uninitialized_fill_n (elems, num, val); } }
Выражение | Описание |
---|---|
uninitialized_fill(beg, end, val) | Инициализация интервала [beg,end) значением val |
uninitialized_fill_n(beg, num, val) | Инициализация num элементов, начиная с beg, значением val |
uninitialized_copy(beg, end, mem) | Инициализация элементов, начиная с mem, злементами интервала [beg,end) |
Функция reserve(), которая резервирует дополнительную память без изменения количества элементов (смотри 130 шаг), может быть реализована так:
namespace std { template <class T, class Allocator> void vector<T,Allocator>::reserve(size_type size) { // Функция reserve() никогда не уменьшает объем памяти if (size <= sizeElems) { return; } // Выделение новой памяти для size элементов T* newmem = alloc.allocate(size); // Копирование старых элементов в новую память uninitialized_copy(elems,elems+numElems,newmem); // Уничтожение старых элементов for (size_type i=0; i<numElems; ++i) { alloc.destroy(&elems[i]); } // Освобождение старой памяти alloc.deallocate(elems,sizeElems); // Элементы находятся в новой памяти sizeElems = size; elems = newmem; } }
На следующем шаге мы рассмотрим инициализирующий итератор.