Однако в большинстве функций могут произойти сбои. Если ошибка возможна, наиболее безопасным будет гарантировать транзакционное поведение функции: либо функция выполняется успешно и программа переходит из начального корректного состояния в корректное целевое состояние, либо — в случае сбоя — программа остается в том же состоянии, в котором находилась перед вызовом функции, т.е. видимые состояния всех объектов после сбойного вызова оказываются теми же, что и до него (например, значение глобальной целой переменной не может измениться с 42 на 43), и любое действие, которое вызывающий код мог предпринять до сбойного вызова, должно остаться возможным (с тем же смыслом) и после сбойного вызова (например, ни один итератор контейнера не должен стать недействительным; применение оператора ++ к упомянутой глобальной целой переменной даст значение 43, а не 44). Такая гарантия безопасности называется строгой.
Наконец, если обеспечить строгую гарантию оказывается слишком сложно или излишне дорого, следует обеспечить, по крайней мере, базовую гарантию: функция либо выполняется успешно и достигает целевого состояния, либо она неуспешна и оставляет программу в состоянии корректном (сохраняя инварианты, за которые отвечает данная функция), но не предсказуемом (это может быть исходное состояние, но может и не быть таковым; некоторые из постусловий могут быть не выполнены; однако все инварианты должны быть восстановлены). Ваше приложение должно быть спроектировано таким образом, чтобы суметь соответствующим образом обработать такой результат работы функции.
Вот и все; более низкого уровня гарантии не существует. Функция, которая не дает даже базовой гарантии — это просто ошибка программиста. Корректная программа должна отвечать, как минимум, базовой гарантии для всех функций. Даже те немногие корректные программы, которые сознательно идут на утечку ресурсов, в частности, в ситуациях, когда программа аварийно завершается, поступают так с учетом того, что ресурсы будут освобождены операционной системой. Всегда разрабатывайте код таким образом, чтобы корректно освобождались все ресурсы, а данные находились в согласованном состоянии даже при наличии ошибок, если только ошибка не приводит к немедленному аварийному завершению программы.
При принятии решения о том, какой уровень гарантии следует поддерживать, надо не забывать о перспективе развития проекта. Всегда проще усилить гарантию в последующих версиях, в то время как снижение степени гарантии ведет к необходимости переделки вызывающего кода, полагающегося на предоставление функцией более строгой гарантии.
Помните, что "небезопасность в отношении ошибок" и "плохое проектирование" идут рука об руку: если некоторую часть кода сложно сделать обеспечивающей базовую гарантию, то почти всегда это говорит о плохом проектировании. Например, если функция отвечает за выполнение нескольких несвязанных задач, ее трудно сделать безопасной в отношении ошибок (см. рекомендацию 5).
Остерегайтесь оператора копирующего присваивания, которому для корректной работы требуется проверка, не выполняется ли присваивание объекта самому себе. Безопасный в отношении ошибок оператор копирующего присваивания автоматически безопасен и в плане присваивания самому себе. Использовать проверку присваивания самому себе можно только в качестве оптимизации, для того чтобы избежать излишней работы (см. рекомендацию 55).
vector
использует непрерывный блок памяти, вставка элемента в средину требует перемещения ряда имеющихся значений на одну позицию для освобождения места для вставляемого элемента. Перемещение выполняется с использованием копирующего конструктора T::T(const T&)
и оператора присваивания T::operator=
, и если одна из этих операций может сбоить (генерировать исключение), то единственный способ обеспечить строгую гарантию — это сделать полную копию контейнера, выполнить операцию над копией, а затем обменять оригинал и копию с использованием бессбойной функции vector
.