Я имею в виду, что назначение-копирование контейнера может попытаться скопировать-назначить исходный распределитель, и если это синтаксическое назначение-копия на самом деле не копирует, то, ну, это все равно, что сказать, что ваш контейнер не делаетt propagate_on_container_copy_assignment
.
Концепция / именованное требование " CopyAssignable " означает нечто большее, чем просто возможность присвоить lvalue объекту того же типа, что иэто значениеОн также имеет семантическое значение: ожидается, что целевой объект будет эквивалентен по значению исходному объекту.Если ваш тип предоставляет оператор присваивания, ожидается, что этот оператор копирует объект.И почти все, что есть в стандартной библиотеке, которая позволяет назначать копии, требует этого.
Если вы задаете для стандартной библиотеки тип, который должен быть CopyAssignable, и у нее есть оператор назначения копирования, который не соблюдаетсемантическое значение этого понятия / именованного требования, неопределенные результаты поведения.
Распределитель имеет своего рода «значение».И копирование распределителя копирует это «значение».Вопрос, который распространяется при копировании / перемещении / замене, в основном задает этот вопрос: является ли значение распределителя частью значения контейнера?Этот вопрос возникает только в связи с контейнерами;при работе с распределителями вообще вопрос спорный.Распределитель имеет значение, и его копирование копирует это значение.Но что это означает относительно ранее выделенного хранилища - это совершенно отдельный вопрос.
Отсюда и черта.
Если бы я написал новый контейнер и /или новый нетривиальный распределитель сегодня могу ли я рассчитывать на семантику распределителя и забыть об этих чертах?
...
Могу ли я написать контейнеры просто так ?и делегировать сложность семантике пользовательских распределителей?:
Контейнер, который нарушает правила AllocatorAwareContainer , не является контейнером, поддерживающим распределитель, и вы не можете разумно передавать распределителик нему, которые следуют стандартной модели распределителя библиотеки.Точно так же распределитель, который нарушает правила Allocator , не может быть разумно передан AllocatorAwareContainer, поскольку эта модель требует, чтобы распределитель действительно был Allocator.Это включает синтаксические и семантические правила.
Если вы не предоставите значения для свойств propagate_on_*
, будет использоваться значение по умолчанию false
.Это означает, что он не будет пытаться распространять ваш распределитель, так что вы не столкнетесь с необходимостью того, что распределитель может быть назначен для копирования / перемещения или для замены.Однако это также означает, что поведение вашего распределителя копирования / перемещения / обмена никогда не будет использоваться, поэтому не имеет значения, какую семантику вы задаете этим операциям.Кроме того, без распространения, если два распределителя были неравны, это означает линейное перемещение / своп во времени.
Однако AllocatorAwareContainer по-прежнему не разрешено игнорировать эти свойства, так как по определению ондолжны реализовать их, чтобы взять на себя эту роль.Если распределитель определяет оператор присваивания копии, но делает его распространение при копировании ложным (что является абсолютно допустимым кодом), вы не можете вызывать оператор присвоения копии распределителя при назначении копирования контейнеру.
По сути, единственный способ выполнить эту работу - это жить в своей собственной вселенной, где вы используете свои «контейнеры» только с вашими «распределителями» и никогда не пытаетесь использовать какие-либо эквиваленты стандартной библиотеки с вашими «контейнерами / распределителями».
Исторический обзор также может быть полезен.
В исторических целях функции propagate_on_*
имели историю изменений до C ++ 11, но это никогда не появилось, как вы предлагаете.
Самая ранняя статья по этому вопросу, которую я могу найти, это N2525 (PDF): специфичное для распределителя поведение Swap и Move * .Основная цель этого механизма состоит в том, чтобы позволить определенным классам итераторов с отслеживанием состояния иметь возможность выполнять операции перемещения и обмена в постоянное время.
Это было некоторое время включено в концептуальную версию, но однажды это былоудаленный из C ++ 0x, он вернулся к классу признаков с новым именем и более упрощенным интерфейсом (PDF) (да, интерфейс, который вы используете сейчас, - простая версия . Не за что;)).
Итак, во всех случаях было явное признание того, что необходимо проводить различие между существованием копирования / перемещения / обмена и смыслом этих операций суважение к контейнеру.
и что обработка этих дел - ужасный код.
Но это не так.В C ++ 17 вы просто используете if constexpr
.В старой версии вы должны полагаться на SFINAE, но это просто означает написание таких функций:
template<typename Alloc>
std::enable_if_t<std::allocator_traits<Alloc>::propagate_on_container_copy_assignment::value> copy_assign_allocator(Alloc &dst, const Alloc &src)
{
dst = src;
}
template<typename Alloc>
std::enable_if_t<!std::allocator_traits<Alloc>::propagate_on_container_copy_assignment::value> copy_assign_allocator(Alloc &dst, const Alloc &src) {}
Наряду с версиями для перемещения и обмена.Затем просто вызовите эту функцию, чтобы выполнить копирование / перемещение / обмен или не выполнять копирование / перемещение / обмен, в соответствии с поведением при распространении.