Шаг 554.
Библиотека STL.Распределители памяти. Использование распределителей при программировании библиотек

    На этом шаге мы рассмотрим использование распределителей при разработке библиотек.

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

    Распределители предоставляют интерфейс для выделения и освобождения памяти, создания и уничтожения объектов (таблица 1).

Таблица 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);
    }
}

Таблица 2. Вспомогательные функции для инициализации неинициализированной памяти
Выражение Описание
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;
    }
}

    На следующем шаге мы рассмотрим инициализирующий итератор.




Предыдущий шаг Содержание Следующий шаг