Это многогранный вопрос, так что несите меня, пока я просматриваю различные аспекты.
Стандартная библиотека ожидает, что все типы пользователей всегда будут предоставлять основную гарантию исключения. Эта гарантия гласит, что при вызове исключения задействованные объекты все еще находятся в действительном, если неизвестно, состоянии, что никакие ресурсы не просачиваются, что никакие фундаментальные инварианты языка не нарушаются, и что на расстоянии не происходило пугающее действие (последнее один из них не является частью формального определения, но это фактически подразумевается неявное предположение).
Рассмотрим конструктора копирования для класса Foo:
Foo(const Foo& o);
Если этот конструктор бросков, основная гарантия исключения дает вам следующие знания:
- Нет нового объект не был создан. Если конструктор выбрасывает, объект не создается.
o
не был изменен. Его единственное участие здесь - это ссылка на const, поэтому его нельзя изменять. Другие случаи подпадают под заголовок «жуткий действие на расстоянии», или, возможно, «фундаментальный языковой инвариант».
- Ресурсов не было утечено, программа в целом по-прежнему согласована.
В конструктор перемещения:
Foo(Foo&& o);
основной гарантией дает меньшую степень уверенности. o
может быть изменен, поскольку он задействован посредством неконстантной ссылки, поэтому он может находиться в любом состоянии.
Далее, посмотрите на vector::resize
. Его реализация, как правило, по той же схеме:
void vector<T, A>::resize(std::size_t newSize) {
if (newSize == size()) return;
if (newSize < size()) makeSmaller(newSize);
else if (newSize <= capacity()) makeBiggerSimple(newSize);
else makeBiggerComplicated(newSize);
}
void vector<T, A>::makeBiggerComplicated(std::size_t newSize) {
auto newMemory = allocateNewMemory(newSize);
constructAdditionalElements(newMemory, size(), newSize);
transferExistingElements(newMemory);
replaceInternalBuffer(newMemory, newSize);
}
Основной функцией здесь является transferExistingElements
. Если мы используем только копирование, у него есть простая гарантия: он не может изменить исходный буфер. Поэтому, если в какой-то момент вызывается операция, мы можем просто уничтожить вновь созданные объекты (имейте в виду, что стандартная библиотека абсолютно не может работать с метанием деструкторов), выбросить новый буфер и реконструировать. Вектор будет выглядеть так, как будто он никогда не был изменен. Это означает, что у нас есть сильная гарантия, хотя конструктор экземпляра элемента предлагает только слабую гарантию.
Но если мы используем перемещение вместо этого, это не сработает. После перемещения одного объекта любое последующее исключение означает, что исходный буфер был изменен. И поскольку у нас нет гарантии, что движущиеся объекты обратно тоже не бросаются, мы даже не можем восстановиться. Таким образом, чтобы сохранить сильную гарантию, мы должны потребовать, чтобы операция перемещения не делала никаких исключений. Если у нас есть это, мы в порядке. И именно поэтому у нас есть move_if_noexcept
.
Что касается разницы между MSVC и GCC: MSVC поддерживает только noexcept
с версии 14, и поскольку он все еще находится в разработке, я подозреваю, что стандартная библиотека не была обновлена, чтобы воспользоваться еще.
путем копирования элементов вектора во время перераспределения вы оставите прежнее хранилище неповрежденным; перемещая их, он изменяется. если выбрано исключение, это нельзя отменить, поскольку может быть выбрано другое исключение –