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

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

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

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

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


person user1678062    schedule 23.09.2012    source источник


Ответы (2)


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

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

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

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

Точнее, в C++11 есть адаптер распределителя с именем scoped_allocator_adaptor:

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

Из С++ 11:

Шаблон класса scoped_allocator_adaptor — это шаблон распределителя, который указывает ресурс памяти (внешний распределитель), который будет использоваться контейнером (как это делает любой другой распределитель), а также указывает ресурс внутреннего распределителя, который должен быть передан конструктору каждого элемента внутри контейнера. . Этот адаптер создается с одним внешним и нулем или более внутренними типами распределителя. Если создается экземпляр только с одним типом распределителя, внутренний распределитель становится самим scoped_allocator_adaptor, таким образом, используя один и тот же ресурс распределителя для контейнера и каждого элемента внутри контейнера и, если сами элементы являются контейнерами, каждый из их элементов рекурсивно. Если создан более чем один распределитель, первый распределитель является внешним распределителем для использования контейнером, второй распределитель передается конструкторам элементов контейнера, и, если сами элементы являются контейнерами, третий распределитель передается в элементы элементы и так далее. Если контейнеры вложены на глубину, превышающую количество распределителей, последний распределитель используется повторно, как и в случае с одним распределителем, для всех оставшихся рекурсий. [Примечание: 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.

person Howard Hinnant    schedule 23.09.2012

Тип распределителя, используемого контейнером, определяется его аргументом конструктора: именно этот тип ожидается в конструкторах контейнера. Однако любой распределитель должен иметь возможность обслуживать типы, отличные от тех, для которых он определен. Например, для 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() их использование может не сработать.

person Dietmar Kühl    schedule 23.09.2012