Контейнер STL: параметр Allocator конструктора и распределенные распределители - программирование
Подтвердить что ты не робот

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

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

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

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

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

4b9b3361

Ответ 1

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

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

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

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

Ну, точнее, С++ 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, что это такое.

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

Ответ 2

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

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

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