На этом шаге мы рассмотрим использование распределителей при разработке библиотек.
Этот шаг посвящен использованию распределителей с точки зрения людей, занимающихся реализацией контейнеров и других компонентов, способных работать с разными распределителями.
Распределители предоставляют интерфейс для выделения и освобождения памяти, создания и уничтожения объектов (таблица 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;
}
}
На следующем шаге мы рассмотрим инициализирующий итератор.