Контейнер STL: параметр Allocator конструктора и распределители области действия

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

После прочтения API Я понял, что есть также возможность указывать распределители в качестве параметра конструктора. Но как мне узнать, какой тип распределителя использует контейнер, если он внутренне связывает данный распределитель из параметра шаблона?

Кроме того, я прочитал, что C ++ 11 теперь использует распределители с областью действия, которые позволяют повторно использовать распределитель контейнера для содержащих его контейнеров. Как реализация контейнера с распределенным ограничителем примерно отличается от реализации, которая не знает о контейнерах с ограниченным объемом?

К сожалению, я не смог найти ничего, что могло бы объяснить это. Спасибо за ответы!

14

Решение

Но как мне узнать, какой тип распределителя использует контейнер, если он
внутренне связывает данный распределитель из параметра шаблона?

Всегда поставлять Allocator<T> в конструктор (где T это value_type контейнера). Контейнер преобразует его в Allocator<U> необходимо где U это некоторая внутренняя структура данных контейнера. Allocator требуется поставлять такие конвертирующие конструкторы, например:

template <class T> class allocator {
...
template <class U> allocator(const allocator<U>&);

Кроме того, я прочитал, что C ++ 11 теперь использует распределители с областью видимости, которые позволяют
повторно использовать распределитель контейнера для содержащих его контейнеров.

Ну, если быть более точным, C ++ 11 имеет адаптер распределителя называется scoped_allocator_adaptor:

template <class OuterAlloc, class... InnerAllocs>
class scoped_allocator_adaptor : public OuterAlloc
{
...
};

Из C ++ 11:

Шаблон класса scoped_allocator_adaptor шаблон распределителя
который определяет ресурс памяти (внешний распределитель), который будет использоваться
контейнер (как и любой другой распределитель), а также определяет внутренний
ресурс распределителя, который будет передан в конструктор каждого элемента
внутри контейнера. Этот адаптер создан с одним внешним и
ноль или более внутренних типов распределителя. Если создан только с одним
тип распределителя, внутренний распределитель становится
scoped_allocator_adaptor сам, таким образом, используя тот же распределитель
ресурс для контейнера и каждого элемента в контейнере и,
если сами элементы являются контейнерами, каждый из их элементов
рекурсивно. Если создается более одного распределителя, первый
allocator — это внешний распределитель для использования контейнером, второй
распределитель передается конструкторам элементов контейнера,
и, если сами элементы являются контейнерами, третий распределитель
передается элементам элементов и так далее. Если контейнеры вложены
на глубину, превышающую количество распределителей, последний распределитель
используется повторно, как в случае с одним распределителем, для любого оставшегося
рекурсии. [Заметка: scoped_allocator_adaptor происходит от
тип внешнего распределителя, так что он может быть заменен внешним распределителем
введите в большинстве выражений. — конечная нота ]

Таким образом, вы получаете поведение распределителей только в том случае, если вы укажете scoped_allocator_adaptor как распределитель для вашего контейнера.

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

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

Например, гостиницы может предоставить член под названием construct это создаст тип по определенному адресу, используя заданные аргументы:

template <class T> class Allocator {
...
template<class U, class... Args>
void construct(U* p, Args&&... args);
};

Если распределитель не предоставляет этот член, allocator_traits обеспечит реализацию по умолчанию. В любом случае, контейнер должен построить все value_typeс помощью этого construct функция, но используя его через allocator_traitsи не используя allocator непосредственно:

allocator_traits<allocator_type>::construct(the_allocator, *ugly details*);

scoped_allocator_adaptor предоставляет обычай construct функции, которые allocator_traits будет пересылать к чему воспользоваться uses_allocator черты и передает правильный распределитель вдоль value_type конструктор. Контейнер остается блаженно неосведомленным об этих деталях. Контейнер должен только знать, что он должен построить value_type с использованием allocator_traits construct функция.

Есть больше деталей, с которыми контейнер должен иметь дело, чтобы правильно обрабатывать распределители с сохранением состояния. Хотя с этими деталями также связано то, что контейнер не делает никаких предположений, а получает все свойства и поведение через allocator_traits, Контейнер даже не может предположить, что pointer является T*, Скорее этот тип найден, спрашивая allocator_traits что это.

Короче говоря, чтобы построить контейнер C ++ 11, изучите allocator_traits, И тогда вы получаете бесплатное поведение распределителя, когда ваши клиенты используют scoped_allocator_adaptor,

13

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

Тип распределителя, используемого контейнером, определяется его аргументом конструктора: именно этот тип ожидается в конструкторах контейнера. Однако любой распределитель должен иметь возможность обслуживать типы, отличные от тех, для которых он определен. Например, для std::list<T, A> ожидаемый распределитель способен выделить T объект, но он никогда не будет использоваться для размещения этих объектов, потому что std::list<T, A> на самом деле нужно выделить узлы. То есть распределитель будет восстановлен для выделения другого типа. К сожалению, это затрудняет использование распределителя для обслуживания определенного типа: вы не знаете тип, который распределитель фактически будет обслуживать.

Что касается распределителей с областью действия, то это работает довольно просто: контейнер определяет, есть ли у него какой-либо член с конструктором, принимающим соответствующий распределитель. Если это так, он перепривязывает распределитель, который использовал, и передает этот распределитель члену. Что не так просто, так это логика, определяющая, используется ли распределитель. Чтобы определить, использует ли член распределитель, черты std::uses_allocator<T, A> используется: определяет, T имеет вложенный typedef allocator_type который и если A может быть преобразован в этот тип. Правила построения объектов-членов описаны в 20.6.7.2 [allocator.uses.construction].

На практике это означает, что распределители полезны для работы с пулом, используемым для контейнера и его элементов. В некоторых контекстах это может также работать разумно, когда выделяются объекты одинакового размера, например для любого из контейнеров на основе узла, чтобы сохранить пул объектов одинакового размера. Однако нет необходимости очищать из шаблона, используемого с распределителем, если они, например, для узлов или некоторых строк, содержащихся в. Кроме того, поскольку использование различных политик выделения может изменить тип, представляется наиболее целесообразным придерживаться распределения по умолчанию или использовать тип распределителя, который является прокси для полиморфного распределителя, фактически определяющего политику выделения. Конечно, в тот момент, когда у вас есть распределители с состоянием, у вас могут быть объекты с разными распределителями и, например, swap()Их использование может не сработать.

4