Шаг 557.
Библиотека STL.
Распределители памяти. Распределитель по умолчанию

    На этом шаге мы рассмотрим распределитель по умолчанию.

    Распределитель по умолчанию объявляется следующим образом:

namespace std {
    template <class T>
    class allocator {
      public:
        // Определения типов
        typedef 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;

        // Привязка распределителя к типу U
        template <class U>
        struct rebind {
            typedef allocator<U> other;
        };

        // Функции возвращают адреса значений
        pointer       address(reference value) const;
        const_pointer address(const_reference value) const;

        // Конструкторы и деструктор
        allocator() throw();
        allocator(const allocator&) throw();
        template <class U>
          allocator(const allocator<U>&) throw();
        ~allocator() throw();

        // Функция возвращает максимальное количество элементов,
        // для которых может быть выделена память
        size_type max_size() const throw();

        // Выделение памяти для num элементов типа T без инициализации
        pointer allocate(size_type num,
                         allocator<void>::const_pointer hint=0);

        // Инициализация элементов выделенного блока p значением value
        void construct(pointer p, const T& value);
        // Удаление элементов инициализированного блока p
        void destroy(pointer p);

        // Освобождение блока p с удаленными элементами
        void deallocate(pointer p, size_type num);
    };
}

    Распределитель по умолчанию выделяет и освобождает память глобальными операторами new и delete. Это означает, что вызов allocate() может сгенерировать исключение bad_alloc. Однако распределитель по умолчанию можно оптимизировать за счет повторного использования освобожденной памяти или выделения лишней памяти для экономии времени на дополнительных перераспределениях. Следовательно, моменты вызова операторов new и delete точно не известны. Возможная реализация пользовательского распределителя приведена в следующем шаге.

    Внутри шаблона распределителя имеется несколько необычное определение шаблонной структуры rebind. Эта шаблонная структура дает возможность косвенного выделения памяти другого типа. Например, если распределитель относится к типу Allocator, то следующая конструкция определяет тип того же распределителя, специализированного для элементов типа Т2:

  Allocator::rethnd<T2>::other

    Представьте, что при реализации контейнера вам потребовалось выделить память для типа, отличного от типа элементов. Например, при реализации дека обычно приходится выделять память для массивов, управляющих блоками элементов (типичная реализация дека представлена на 141 шаге). Следовательно, вам понадобится распределитель для выделения массивов указателей на элементы:

namespace std {
    template <class T,
              class Allocator = allocator<T> >
    class deque {
      ...
      private:
        // Привязка распределителя к типу T*
        typedef typename Allocator::rebind<T*>::other PtrAllocator;

        Allocator    alloc;        // Распределитель для значений типа T
        PtrAllocator block_alloc;  // Распределитель для значений типа T*
        T**          elems;        // Массив блоков элементов
        ...
    };
}

    При управлении элементами дека один распределитель должен работать с блоками элементов, а другой - с массивом блоков элементов. Последний относится к типу PtrAllocator. При помощи структуры rebind<> распределитель элементов (Allocator) привязывается к типу массива элементов (Т*).

    Распределитель по умолчанию имеет следующую специализацию для типа void:

namespace std {
    template <>
    class allocator<void> {
      public:
        typedef void*       pointer;
        typedef const void* const_pointer;
        typedef void        value_type;
        template <class U>
        struct rebind {
            typedef allocator<U> other;
        };
    };
}

    На следующем шаге мы рассмотрим пользовательский распределитель.




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