Являются ли С++ 11 ядерными распределителями взаимозаменяемыми через границы типов?

Мой вопрос здесь в основном заключается в следующем:

Как я могу написать stateful allocator в С++ 11, учитывая требования к построению копии?

В принципе, несмотря на то, что в стандарте С++ 11 теперь используются контроллеры с установленным состоянием, мы все еще имеем требование: если вы скопируете определенный Allocator, копия должна сравняться с помощью оператора == с оригиналом, Это указывает на то, что копия может безопасно освободить память, которая была выделена оригиналом, и наоборот.

Итак, сразу с места в карьер, это уже запрещает распределителю поддерживать уникальное внутреннее состояние, например, распределитель slab-памяти или пул памяти или что-то в этом роде. Одним из решений было бы использовать идиому shared_ptr для указателя на реализацию для внутреннего состояния, чтобы все копии некоторого исходного Allocator использовали один и тот же базовый пул памяти. Это не так уж плохо. За исключением...

В соответствии с вышеупомянутым упомянутым вопросом, а также принятым ответом стандарт также требует, чтобы Allocator<T> имел конструктор совместимых копий с Allocator<U>, так что:

Allocator<T> alloc1;
Allocator<U> alloc2(alloc1);
assert(alloc1 == alloc2); // must hold true

Итак, другими словами, типы распределителей должны быть совместимы независимо от разных параметров шаблона. Это означает, что если я выделяю некоторую память с помощью Allocator<T>, я должен уметь освобождать эту память с помощью экземпляра Allocator<U>, построенного из оригинала Allocator<T>.

... и это в значительной степени стоп-шоу для любой попытки написать распределитель, который использует какой-то пул памяти на основе размера, такой как пул simple_segregated_storage, который возвращает только куски определенного размера на основе sizeof(T).

Но... это правда?

Я понимаю, что для Allocator<T>::rebind требуется конструктор совместимых копий, поэтому пользователям контейнеров не нужно знать внутренние данные, например, тип связанного списка node или что-то еще. Но, насколько я понимаю, стандарт, похоже, не говорит так драконовски, как требование что a Allocator<U>, построенный из Allocator<T>, должен сравниваться с исходным экземпляром Allocator<T>.

Стандарт в основном требует следующей семантики, где X - это тип Allocator<T>, a1 и a2 - это экземпляры X, Y - это тип Allocator<U>, а b - это экземпляр Allocator<U>.

От: § 17.6.3.5 (требования к подписчикам)

a1 == a2 returns true only if storage allocated from each can be deallocated via the other.

operator == shall be reflexive, symmetric, and transitive, and shall not exit via an exception.

a1 != a2 : same as !(a1 == a2)

a == b : same as a == Y::rebind<T>::other(b)

a != b : same as !(a == b)

X a1(a); Shall not exit via an exception. post: a1 == a

X a(b); Shall not exit via an exception. post: Y(a) == b, a == X(b)


Итак, как я это читал, экземпляры Allocator<T>, построенные из Allocator<U>, не обязательно взаимозаменяемы. Стандарт просто требует, чтобы a == b был эквивалентен Y(a) == b, не, что a == b должен быть правдой!

Я думаю, что требование для конструктора кросс-контурной границы делает это запутанным. Но, как я это прочитал, если у меня есть Allocator<T>, он должен иметь конструктор копирования, который принимает Allocator<U>, но это не означает, что:

Allocator<T> alloc1;
Allocator<U> alloc2(alloc1);
assert(alloc2 == alloc1); 

Другими словами, способ, которым я читал это, вышеприведенное утверждение разрешено. Но я не уверен в своем понимании здесь, потому что:

  • принятый ответ на этот вопрос говорит об обратном, а ответчик - парень с репутацией 108K

  • Взаимодействие между требованиями конструктора копий и требованиями к равенству в стандарте несколько запутанно, и я могу неправильно понимать формулировку.

Итак, я здесь правильно? (Кстати, реализация boost::pool_allocator, по-видимому, подразумевает, что я прав, полагая, что разработчик повышения заботится о соблюдении стандартов, поскольку этот распределитель не взаимозаменяемые по типу границ.)

Ответ 1

Последняя строка, которую вы указываете:

X a(b); Не выходить через исключение. сообщение: Y(a) == b, a == X(b)

Конфликты с вашим заключением.

using X = Allocator<T>;
using Y = Allocator<U>;
Y b;
X a(b);
assert(Y(a) == b);
assert(a == X(b));
// therefore
assert(a == b);