Какая гарантия безопасности для вектора:: insert?

Мне интересно, какая именно гарантия безопасности для std::vector::insert? Меня интересуют как один аргумент, так и перегрузка диапазона этой функции.

Ответ 1

Точная гарантия приведена в С++ 11 23.3.6.5:

Если исключение выбрано иначе, чем конструктор копирования, переместите конструктор, оператор присваивания или оператор присваивания перемещения T или любой из операций InputIterator, никаких эффектов нет. Если исключение вызывается конструктором перемещения не CopyInsertable T, эффекты не заданы.

Ответ 2

Как правило, одноэлементная форма insert имеет сильную гарантию исключения для любого контейнера в соответствии с [container.requirements.general]/10, но vector::insert является исключением из этого правила:

[vector.modifier]/1 применяется к vector::insert; InputIterator здесь относится к перегрузке insert, которая вставляет диапазон.

Если исключение генерируется иначе, чем конструктор копирования, переместите конструктор, оператор присваивания или оператор присваивания перемещения T или любой из операций InputIterator, никаких эффектов нет. Если исключение выбрано конструктором move не CopyInsertable T, последствия не заданы.


vector является контейнером, поддерживающим распределитель, что означает, что он использует распределитель для распределения памяти и построения его элементов [container.requirements.general]/3

Для компонентов, затронутых этим подпунктом, объявляющим allocator_type, объекты, хранящиеся в этих компонентах, должны быть построены с использованием функции allocator_traits<allocator_type>::construct и уничтожены с помощью функции allocator_traits<allocator_type>::destroy. Эти функции вызываются только для типа элемента контейнера, а не для внутренних типов, используемых контейнером.

Я думаю, это означает, что локальные объекты, которые не являются элементами контейнера, могут быть созданы без использования распределителя (например, для копирования и замены). В противном случае требования к ctors типа значения были бы бессмысленными; функция распределителя construct может иметь разные гарантии исключения, чем тип значения ctor.


CopyInsertable указан в [container.requirements.general]/13, требуя, чтобы

allocator_traits<A>::construct(m, p, v);

хорошо сформирован; где A - тип распределителя, m имеет тип A, p является указателем на T, v является выражением типа (const) T и T - тип значения контейнера. Это встроенная конструкция из одного аргумента (копирование или перемещение).

Аналогично, указывается MoveConstructible, но v является (всегда) значением r типа T. EmplaceConstructible следует за тем же самым формаем для нуля или более аргументов вместо v.

Функция insert для контейнеров последовательностей предъявляет различные требования к типу значений для различных форм [sequence.reqmts]; здесь включая дополнительные требования для vector:

  • для одного аргумента, который является lvalue типа (const) T, а для формы вставить N копий, T должно быть CopyInsertable и CopyAssignable
  • для одного аргумента, являющегося значением r T, T должно быть MoveInsertable и MoveAssignable
  • для формы диапазона, T должен быть EmplaceConstructible от разыменованных итераторов (*); кроме того, MoveInsertable и MoveAssignable, если итераторы диапазона не являются итераторами вперед

(*) Примечание: EmplaceConstructible только из разыменованных итераторов недостаточно, если контейнер необходимо изменить для вставки (и, например, *i для такого итератора не относится к типу значения). Возможно, что спецификация требует, чтобы форма диапазона наследовала требования одноэлементной формы, то есть либо MoveAssignable, либо CopyAssignable.

Боковое примечание: Форма диапазона insert требует, чтобы два итератора не указывали в контейнере, в который они должны быть вставлены.


Я интерпретирую спецификации исключений следующим образом:

Дополнительный оператор CopyInsertable в спецификации исключения vector::insert, вероятно, различает базовую гарантию и не гарантирует: dtor контейнера обычно требуется вызвать dtor всех элементов и освободить всю память (в общие требования к контейнерам). То есть, если поведение не определено /undefined, основная гарантия сохраняется.

Почему существует требование, которое объединяет CopyInsertable и move-ctor (вместо allocator::construct с rvalue), я не знаю. Перемещение-ctor используется только для объектов, которые не являются элементами контейнера (косвенно, возможно, через allocator::construct).

Другие замечания о "отсутствии эффектов" (- > сильная гарантия) не применяются к операциям распределителя (construct). Очевидно, что операции распределителя не должны быть исключены. Поскольку вы можете предоставить нестандартный распределитель, который не использует тип значения copy- и move-ctor для construct, insert должен обеспечить надежные исключения даже для операций распределителя construct. Например, если распределитель construct не является лишним, во время изменения размера элементы не могут быть построены по ходу, а должны быть скопированы.

Перемещение и назначение копии должны быть исключены из-за сильной гарантии, поскольку элементы, возможно, придется перемещать на insert; copy-and move-ctor, возможно, не должно быть исключением из-за сильной гарантии из-за локальных объектов, созданных в алгоритме.

Ответ 3

Если метод insert вставляет один элемент в конец списка и не требует выделения какой-либо памяти, он предлагает надежная гарантия исключения.

Если он должен добавить несколько элементов или должен выделить память, он предлагает базовую гарантию исключения. Boost содержит хорошее описание гарантий исключения.

Основная гарантия: сохранение инвариантов компонента и утечка ресурсов.     Сильная гарантия: операция успешно завершилась или выбрала исключение, оставив состояние программы точно так, как было до начала операции.      "Без броска" гарантирует, что операция не будет генерировать исключение.

Это означает, что после исключения вы знаете, что vector будет использоваться, но он может не иметь всех данных, которые вы вставили в него. Все объекты, которые были успешно вставлены, будут полностью построены.