Каждый класс распределителя должен иметь интерфейс, подобный следующему:
template<class T>
class allocator
{
...
template<class Other>
struct rebind { typedef allocator<Other> other; };
};
И занятия, которые использование распределители делают что-то избыточное, как это:
template<class T, class Alloc = std::allocator<T> >
class vector { ... };
Но почему это необходимо?
Другими словами, они не могли просто сказать:
template<class T>
class allocator { ... };
template<class T, template<class> class Alloc = std::allocator>
class vector { ... };
что является одновременно более элегантным, менее избыточным и (в некоторых аналогичных ситуациях) потенциально более безопасным?
Почему они пошли rebind
маршрут, который также вызывает больше избыточности (то есть вы должны сказать, T
дважды)?
(Подобный вопрос char_traits
а остальные … хотя у них не все rebind
, они все еще могут извлечь выгоду из параметров шаблона шаблона.)
Но это не сработает, если вам нужно более 1 параметра шаблона!
На самом деле, это работает очень хорошо!
template<unsigned int PoolSize>
struct pool
{
template<class T>
struct allocator
{
T pool[PoolSize];
...
};
};
Сейчас если vector
был определен только так:
template<class T, template<class> class Alloc>
class vector { ... };
Тогда вы могли бы просто сказать:
typedef vector<int, pool<1>::allocator> int_vector;
И это будет работать отлично, без нуждаюсь в тебе (излишне) сказать int
дважды.
И rebind
операция внутри vector
просто стал бы Alloc<Other>
вместо Alloc::template rebind<Other>::other
,
Цитированный текст из Основы алгоритмов в C ++ 11, Том 1, глава 4, с. 35:
template <typename T>
struct allocator
{
template <typename U>
using rebind = allocator<U>;
};
пример использования:
allocator<int>::rebind<char> x;
В Язык программирования C ++, 4-е издание, раздел 34.4.1, с. 998, комментируя «классический» элемент повторного связывания в классе распределителя по умолчанию:
template<typename U>
struct rebind { using other = allocator<U>;};
Бьярне Страуструп пишет:
Любопытный шаблон повторной привязки — архаичный псевдоним. Это должно было быть:
template<typename U> using other = allocator<U>;
Однако allocator был определен до того, как такие псевдонимы были поддержаны C ++.
Но почему это необходимо?
Что если ваш класс распределителя имеет более одного аргумента шаблона?
Это в значительной степени объясняется тем, почему обычно не рекомендуется использовать аргументы шаблона в пользу использования обычных аргументов шаблона, даже если это означает небольшую избыточность на сайте создания экземпляров. Во многих случаях (однако, вероятно, не для распределителей) этот аргумент не всегда может быть шаблоном класса (например, нормальный класс с функциями-членами шаблона).
Возможно, вам будет удобно (в рамках реализации класса контейнера) использовать параметр шаблона шаблона только потому, что он упрощает некоторые из внутренних синтаксисов. Однако, если у пользователя есть шаблон класса с несколькими аргументами в качестве распределителя, который он хочет использовать, но вы требуете от пользователя предоставления распределителя, который является шаблоном класса с одним аргументом, вы фактически заставите его создать оболочку для почти любой новый контекст, в котором он должен использовать этот распределитель. Это не только невероятно, но и очень неудобно. И на данный момент это решение далеко не является «элегантным и менее избыточным» решением, которое вы изначально предполагали. Скажем, у вас есть распределитель с двумя аргументами, какой из следующих вариантов является наиболее простым для пользователя?
std::vector<T, my_allocator<T,Arg2> > v1;
std::vector<T, my_allocator_wrapper<Arg2>::template type > v2;
По сути, вы заставляете пользователя создавать множество бесполезных вещей (обертки, псевдонимы шаблонов и т. Д.) Просто для удовлетворения требований вашей реализации. Требовать от автора пользовательского класса-распределителя предоставления вложенного шаблона повторного связывания (который является просто тривиальным псевдонимом шаблона) гораздо проще, чем все искажения, которые требуются при альтернативном подходе.
В вашем подходе вы заставляете распределитель быть шаблоном с одним параметром, что может быть не всегда так. Во многих случаях распределители могут быть не шаблонными, а вложенными rebind
может вернуть тот же тип распределителя. В других случаях распределитель может иметь дополнительные аргументы шаблона. Этот второй случай является случаем std::allocator<>
который, как и все шаблоны в стандартной библиотеке, может иметь дополнительные аргументы шаблона, если реализация предоставляет значения по умолчанию. Также обратите внимание, что существование rebind
необязательно в некоторых случаях, когда allocator_traits
может быть использован для получения типа отскока.
Стандарт на самом деле упоминает, что вложенный rebind
на самом деле это просто шаблонный typedef:
§17.6.3.5 / 3
Примечание A: Повторная привязка шаблона класса члена в таблице выше
эффективно шаблон typedef. [Примечание: в общем, если имя
Распределитель связан сSomeAllocator<T>
, затем
Allocator::rebind<U>::other
тот же тип, что иSomeAllocator<U>
,
гдеsomeAllocator<T>::value_type
я стоюSomeAllocator<U>::value_type
is U. — конец примечания] Если Allocator является шаблоном класса
создание формыSomeAllocator<T, Args>
где Args равен нулю
или более аргументов типа, и Allocator не предоставляет члена перепривязки
шаблон, который использует стандартный шаблон allocator_traitsSomeAllocator<U, Args>
на местеAllocator:: rebind<U>::other
по умолчанию. За
типы распределителей, которые не являются шаблонами
Форма, по умолчанию не предоставляется.
Предположим, вы хотите написать функцию, принимающую все виды векторов.
Тогда гораздо удобнее писать
template <class T, class A>
void f (std::vector <T, A> vec) {
// ...
}
чем писать
template <class T, template <class> class A>
void f (std::vector <T, A> vec) {
// ...
}
В большинстве случаев такая функция в любом случае не заботится о распределителе.
Далее отметим, что распределители не обязательно должны быть шаблоном. Вы можете написать отдельные классы для определенных типов, которые должны быть выделены.
Еще более удобный способ разработки распределителей, вероятно, был бы
struct MyAllocator {
template <class T>
class Core {
// allocator code here
};
};
Тогда можно было бы написать
std::vector <int, MyAllocator> vec;
а не несколько вводящее в заблуждение выражение
std::vector <int, MyAllocator<int> > vec;
Я не уверен, что выше MyAllocator
разрешено использовать в качестве распределителя после добавления rebind
то есть, является ли следующее действительным классом распределителя:
struct MyAllocator {
template <class T>
class Core {
// allocator code here
};
template <class T>
struct rebind { using other=Core<T>; };
};