Функции std::move/copy
, основанные на диапазоне, не способны обеспечить надежную исключительную гарантию. В случае исключения вам нужен итератор для последнего элемента, который был успешно скопирован / перемещен, чтобы вы могли отменить все правильно. Вы должны сделать копирование / перемещение вручную (или написать специальную функцию для этого).
Что касается подробностей вашего вопроса, то стандарт на самом деле не касается того, что должно произойти, если construct
выдает исключение, которое не выдается из конструктора объекта, являющегося конструкцией. Цель стандарта (по причинам, которые я объясню ниже), вероятно, заключается в том, что это обстоятельство никогда не должно происходить. Но мне еще предстоит найти какое-либо утверждение в стандарте по этому поводу. Итак, давайте на минутку предположим, что это должно быть возможно.
Чтобы контейнеры с распределителем могли предоставить гарантию строгих исключений, construct
, по крайней мере, не должно выдавать после построения объекта. В конце концов, вы не знаете, какое исключение было сгенерировано, иначе вы бы не смогли определить, был ли объект успешно построен или нет. Это сделает реализацию стандартного требуемого поведения невозможной. Итак, давайте предположим, что пользователь не сделал что-то, что делает реализацию невозможной.
Учитывая это обстоятельство, вы можете написать свой код, предполагая, что любое исключение, генерируемое construct
, означает, что объект не был построен. Если construct
выдает исключение, несмотря на наличие аргументов, которые будут вызывать конструктор noexcept
, то вы предполагаете, что конструктор никогда не вызывался. И вы пишете свой код соответственно.
В случае копирования вам нужно только удалить все уже скопированные элементы (в обратном порядке, конечно). Ход дела немного сложнее, но все же вполне выполнимо. Вы должны переместить-назначить каждый успешно перемещенный объект обратно в исходное положение.
Проблема? vector<T>::*_back
не требует, чтобы T
был MoveAssignable. Требуется только, чтобы T
был Move Insertable : то есть вы можете использовать распределитель для создания их в неинициализированной памяти. Но вы не перемещаете это в неинициализированную память; вам нужно переместить его туда, где уже существует перемещенный T
. Таким образом, чтобы сохранить это требование, вам необходимо уничтожить все T
, которые были успешно перемещены, а затем переместить их обратно на место.
Но поскольку MoveInsertion требует использования construct
, который, как было установлено ранее, может выдать ... упс . В самом деле, именно это очень точно , поэтому функции перераспределения vector
не перемещаются , если тип не перемещается или не копируется (и если это последний случай, вы не получаете гарантию сильного исключения).
Так что мне кажется вполне очевидным, что любой метод распределителя construct
, как ожидается, по стандарту будет генерировать только если выбрасывает выбранный конструктор. В vector
нет другого способа реализовать требуемое поведение. Но, учитывая, что явного утверждения этого требования не существует, я бы сказал, что это является дефектом в стандарте. И это не новый дефект, так как я просмотрел стандарт C ++ 17, а не рабочий документ.
Очевидно, что с 2014 года этот вопрос стал проблемой LWG, и решение этой проблемы было ... хлопотным.