Почему необходим allocator:: rebind, когда у нас есть параметры шаблона шаблона?
Каждый класс распределителя должен иметь интерфейс, похожий на следующий:
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
, они все равно могут воспользоваться параметрами шаблона шаблона.)
Edit:
Но это не сработает, если вам понадобится более одного параметра шаблона!
Собственно, он работает очень хорошо!
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
.
Ответы
Ответ 1
Цитата из Основы алгоритмов в С++ 11, том 1, глава 4, с. 35:
template <typename T>
struct allocator
{
template <typename U>
using rebind = allocator<U>;
};
Использование образца:
allocator<int>::rebind<char> x;
В языке программирования С++, четвертое издание, раздел 34.4.1, стр. 998, комментируя "классический" элемент обратной связи в классе распределителя по умолчанию:
template<typename U>
struct rebind { using other = allocator<U>;};
Бьярне Страуструп пишет следующее:
Любопытный шаблон перезаписи - это архаичный псевдоним. Это должно было быть:
template<typename U>
using other = allocator<U>;
Однако распределитель был определен до того, как такие псевдонимы были поддержаны С++.
Ответ 2
Но зачем это нужно?
Что делать, если ваш класс распределителя содержит более одного аргумента шаблона?
В значительной степени это связано с тем, почему обычно не рекомендуется использовать аргументы шаблона шаблона в пользу использования обычных аргументов шаблона, даже если это означает немного избыточности на сайте-экземпляре. Во многих случаях (но, вероятно, не для распределителей) этот аргумент может не всегда быть шаблоном класса (например, нормальным классом с функциями члена шаблона).
Вам может показаться удобным (в рамках реализации класса контейнера) использовать параметр шаблона шаблона только потому, что он упрощает некоторые внутренние синтаксисы. Однако, если у пользователя есть шаблон класса с несколькими аргументами в качестве распределителя, который он хочет использовать, но вы требуете от пользователя предоставить распределитель, который является шаблоном класса с одним аргументом, вы фактически заставите его создать оболочку для почти любой новый контекст, в котором он должен использовать этот распределитель. Это не только невозможно, но и неудобно. И на данный момент это решение далеко не является "изящным и менее избыточным" решением, которое вы изначально предполагали. Скажем, у вас был распределитель с двумя аргументами, какой из следующих является самым простым для пользователя?
std::vector<T, my_allocator<T,Arg2> > v1;
std::vector<T, my_allocator_wrapper<Arg2>::template type > v2;
Вы в основном заставляете пользователя создавать много бесполезных вещей (обертки, псевдонимы шаблонов и т.д.) только для удовлетворения ваших потребностей в реализации. Требование, чтобы автор настраиваемого класса распределителя для поставки вложенного шаблона повторной обработки (который является просто тривиальным псевдонимом шаблона) намного проще, чем все требуемые искажения с альтернативным подходом.
Ответ 3
В вашем подходе вы вынуждаете распределитель быть шаблоном с одним параметром, что может быть не всегда так. Во многих случаях распределители могут быть не-шаблонами, а вложенный rebind
может возвращать один и тот же тип распределителя. В других случаях распределитель может иметь дополнительные аргументы шаблона. Этот второй случай - это случай std::allocator<>
, который, поскольку всем шаблонам в стандартной библиотеке разрешено иметь дополнительные аргументы шаблона, пока реализация предоставляет значения по умолчанию. Также обратите внимание, что существование rebind
является необязательным в некоторых случаях, где allocator_traits
можно использовать для получения типа отскока.
В стандарте фактически упоминается, что вложенный rebind
на самом деле просто шаблонный typedef:
§17.6.3.5/3 Примечание A: Повторение шаблона шаблона класса участников в приведенной выше таблице эффективно шаблон typedef. [Примечание: в целом, если имя Allocator привязан к SomeAllocator<T>
, затем Allocator::rebind<U>::other
- это тот же тип, что и SomeAllocator<U>
, где someAllocator<T>::value_type
- T, а SomeAllocator<U>::value_type
- U. - примечание к концу] Если Allocator является шаблоном класса создание формы SomeAllocator<T, Args>
, где Args равно нулю или более аргументов типа, а Allocator не предоставляет элемент повторной привязки шаблон, стандартный шаблон allocator_traits использует SomeAllocator<U, Args>
вместо Allocator:: rebind<U>::other
по умолчанию. Для типы распределителей, которые не являются экземплярами шаблонов выше форма, по умолчанию не предоставляется.
Ответ 4
Предположим, вы хотите написать функцию, которая принимает все виды векторов.
Тогда гораздо удобнее писать
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>; };
};