Мой вопрос здесь в основном заключается в следующем:
Как я могу написать 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
, по-видимому, подразумевает, что я прав, полагая, что разработчик повышения заботится о соблюдении стандартов, поскольку этот распределитель не взаимозаменяемые по типу границ.)