Если задать какому-нибудь ИИ простой вопрос о гарантиях безопасности исключений оператора присваивания для std::vector, то можно получить однозначный ответ: мол, обеспечивается строгая гарантия. Т.е. если в процессе работы оператора копирования возникнет исключение, то целевой вектор останется в своем исходном виде.
Однако, не все так однозначно™
Пункт первый, далеко не очевидный
std::vector зависит от аллокатора. Хотя, наверное, мало кому доводилось использовать std::vector с аллокатором, отличным от std::allocator. Тем не менее, у вектора есть аллокатор. А у аллокатора есть такое свойство как propagate_on_container_copy_assignment (см., например, здесь). И если это свойство предписывает скопировать в вектор-приемник аллокатор из вектора-источника, то тут возникает тонкий момент: старое содержимое вектора-источника должно быть удалено посредством старого аллокатора.
Если глянуть как этот момент учитывается в реализациях стандартной библиотеки (libstdc++ от GCC или libcxx от LLVM), то можно увидеть, что сперва уничтожается старое содержимое вектора, и лишь затем делается попытка копирования содержимого вектора источника.
Особенно хорошо это видно на примере libstdc++v3:
Вначале удаляется старое содержимое вектора-приемника, а затем (когда вектор-приемник стал пустым) выделяется новый блок памяти куда копируется содержимое вектора-источника.
Получается, что если при аллокации нового блока у нас возникнет исключение, что вектор-приемник окажется пустым. А это всего лишь базовая гарантия безопасности исключений, а не строгая.
Пункт второй, более очевидный
Многое зависит еще и от того, какие гарантии безопасности исключений дает сам тип T, который хранится в vector-е. Если посмотреть на одну из ветвей работы оператора присваивания в libstdc++v3, то можно увидеть, что новое содержимое записывается поверх старого:
А раз так, то мы сильно зависим от того, бросают ли исключения операторы копирования у T. Если бросают, то может случиться следующее:
- первые N элементов из вектора-источника будут скопированы;
- на (N+1) элементе возникнет исключение, операция копирования будет прервана.
Получится, что первые N элементов у вектора-приемника получат новые значения, а оставшиеся -- сохранят старые. При это непонятно что будет с (N+1): если у T::operator= строгая гарантия безопасности исключений, то сохранится старое. А вот если нет... Тогда этот элемент окажется в непонятном состоянии.
В сухом же остатке имеем то, что если в операторе копирования для вектора возникнет исключение в T::operator=, то вектор может изменить свое состояние (часть будет иметь новое значение, часть старое). А это никакая не строгая гарантия безопасности.
В общем, как мне думается, если мы хотим гарантировать себе откат вектора-приемника к исходному виду при присваивании, то следует делать что-то вроде:
|
template<typename T, typename Alloc> void strong_guarantee_copy(vector<T, Alloc> & dest, const vector<T, Alloc> & src) { vector<T, Alloc> fresh_copy{ src, dest.get_allocator() }; swap(dest, fresh_copy); } |
В этом случае мы сперва получаем копию. Или исключение, если копия не создается по каким-либо причинам. А уже потом перемещаем это новое значение в dest.
Хотя, в случае с кастомными аллокаторами может быть не так радостно. Но об этом я бы хотел поговорить в другой раз.




Комментариев нет:
Отправить комментарий