KnigaRead.com/
KnigaRead.com » Компьютеры и Интернет » Программирование » Герб Саттер - Стандарты программирования на С++. 101 правило и рекомендация

Герб Саттер - Стандарты программирования на С++. 101 правило и рекомендация

На нашем сайте KnigaRead.com Вы можете абсолютно бесплатно читать книгу онлайн Герб Саттер, "Стандарты программирования на С++. 101 правило и рекомендация" бесплатно, без регистрации.
Перейти на страницу:

Наконец, если обеспечить строгую гарантию оказывается слишком сложно или излишне дорого, следует обеспечить, по крайней мере, базовую гарантию: функция либо выполняется успешно и достигает целевого состояния, либо она неуспешна и оставляет программу в состоянии корректном (сохраняя инварианты, за которые отвечает данная функция), но не предсказуемом (это может быть исходное состояние, но может и не быть таковым; некоторые из постусловий могут быть не выполнены; однако все инварианты должны быть восстановлены). Ваше приложение должно быть спроектировано таким образом, чтобы суметь соответствующим образом обработать такой результат работы функции.

Вот и все; более низкого уровня гарантии не существует. Функция, которая не дает даже базовой гарантии — это просто ошибка программиста. Корректная программа должна отвечать, как минимум, базовой гарантии для всех функций. Даже те немногие корректные программы, которые сознательно идут на утечку ресурсов, в частности, в ситуациях, когда программа аварийно завершается, поступают так с учетом того, что ресурсы будут освобождены операционной системой. Всегда разрабатывайте код таким образом, чтобы корректно освобождались все ресурсы, а данные находились в согласованном состоянии даже при наличии ошибок, если только ошибка не приводит к немедленному аварийному завершению программы.

При принятии решения о том, какой уровень гарантии следует поддерживать, надо не забывать о перспективе развития проекта. Всегда проще усилить гарантию в последующих версиях, в то время как снижение степени гарантии ведет к необходимости переделки вызывающего кода, полагающегося на предоставление функцией более строгой гарантии.

Помните, что "небезопасность в отношении ошибок" и "плохое проектирование" идут рука об руку: если некоторую часть кода сложно сделать обеспечивающей базовую гарантию, то почти всегда это говорит о плохом проектировании. Например, если функция отвечает за выполнение нескольких несвязанных задач, ее трудно сделать безопасной в отношении ошибок (см. рекомендацию 5).

Остерегайтесь оператора копирующего присваивания, которому для корректной работы требуется проверка, не выполняется ли присваивание объекта самому себе. Безопасный в отношении ошибок оператор копирующего присваивания автоматически безопасен и в плане присваивания самому себе. Использовать проверку присваивания самому себе можно только в качестве оптимизации, для того чтобы избежать излишней работы (см. рекомендацию 55).

Примеры

Пример 1. Повторная попытка после сбоя. Если ваша программа включает команду для сохранения данных в файл и во время записи произошел сбой, убедитесь, что вы вернулись к состоянию, когда операция может быть повторена. В частности, не освобождайте никакие структуры данных до тех пор, пока данные не будут полностью сброшены на диск. Это не теоретизирование — нам известен один текстовый редактор, который не позволяет изменить имя файла для сохранения данных после ошибки записи.

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

Пример 3. std::vector::insert. Поскольку внутреннее представление vector<T> использует непрерывный блок памяти, вставка элемента в средину требует перемещения ряда имеющихся значений на одну позицию для освобождения места для вставляемого элемента. Перемещение выполняется с использованием копирующего конструктора T::T(const T&) и оператора присваивания T::operator=, и если одна из этих операций может сбоить (генерировать исключение), то единственный способ обеспечить строгую гарантию — это сделать полную копию контейнера, выполнить операцию над копией, а затем обменять оригинал и копию с использованием бессбойной функции vector<T>::swap.

Однако выполнение всех указанных действий обходится излишне дорого как в плане требуемой памяти, так и процессорного времени. Таким образом, обеспечение строгой гарантии даже тогда, когда она не является необходимой, оказывается чрезмерно расточительным. Поэтому строгую гарантию вызывающий код должен при необходимости обеспечивать самостоятельно — для этого шаблон vector предоставляет все необходимые инструменты. (В лучшем случае, если тип содержимого контейнера не генерирует исключений в копирующем конструкторе и копирующем операторе присваивания, никаких дополнительных действий не требуется. В худшем следует создать копию вектора, выполнить вставку в копию и после успешного завершения данной операции обменять копию и исходный вектор.)

Пример 4. Запуск спутника. Рассмотрим функцию f, в которой частью ее работы является запуск спутника, и используемую ею функцию LaunchSatellite, обеспечивающую гарантию не ниже строгой. Если функция f может выполнить всю работу, при которой может произойти сбой, до запуска спутника, то f способна обеспечить строгую гарантию. Но если f должна выполнить некоторые операции, в процессе которых может произойти сбой, уже после запуска спутника, то обеспечение строгой гарантии оказывается невозможным — вернуть запущенный спутник на стартовую площадку уже нельзя. (Такую функцию f следует разделить по крайней мере на две, поскольку одна функция не должна даже пытаться выполнить несколько различных действий такой важности; см. рекомендацию 5.)

Ссылки

[Abrahams96] • [Abrahams01b] • [Alexandrescu03d] • [Josuttis99] §5.11.2 • [Stroustrup00] §14.4.3, §E.2-4, §E.6 • [Sutter00] §8-19, §40-41, §47 • [Sutter02] §17-23 • [Sutter04] §11-13 • [Sutter04b]

72. Для уведомления об ошибках следует использовать исключения

Резюме

Для уведомления об ошибках лучше использовать механизм исключений, а не коды ошибок. Применять коды состояния (например, коды ошибок, переменную errno) следует только тогда, когда нельзя использовать исключения (см. рекомендацию 62), а также для ситуаций, которые не являются ошибками. К другим методам, таким как экстренное завершение программы (или плановое завершение с освобождением ресурсов и т.п. действиями), следует прибегать только в ситуациях, когда восстановление после ошибки невозможно (или не требуется).

Обсуждение

То, что современные языки программирования, созданные в течение последних 20 лет, используют в качестве основного механизма сообщения об ошибках исключения, — не случайность. Практически по определению исключения предназначены для уведомления об исключениях в нормальном процессе — известных также как "ошибки", которые определены в рекомендации 70 как нарушения предусловий, постусловий и инвариантов. Так же, как и все другие механизмы уведомления об ошибках, исключения не должны генерироваться при нормальной успешной работе.

Далее мы будем использовать термин "коды состояния" для всех видов сообщения об ошибках посредством кодов (включая коды возврата, errno, функцию GetLastError и прочие стратегии возврата или получения кодов), а термин "коды ошибок" — для тех кодов состояния, которые означают ошибки. В С++ сообщение об ошибках посредством исключений имеет явные преимущества перед уведомлением посредством кодов ошибок.

• Исключения невозможно проигнорировать. Самое слабое место кодов ошибок заключается в том, что по умолчанию они игнорируются; чтобы уделить хотя бы минимальное внимание кодам ошибок, вы должны явно писать код, который опрашивает код ошибки и отвечает на него. Весьма распространенная среди программистов практика — случайно (или из-за лени) забыть опросить код ошибки. Исключения же невозможно просто проигнорировать; чтобы проигнорировать исключение, вы должны явно перехватить его (даже если вы сделаете это при помощи единственной инструкции catch(...)) и никак на него не отреагировать.

• Исключения распространяются автоматически. Коды ошибок по умолчанию за пределы области видимости не распространяются; для того, чтобы информировать высокоуровневую вызывающую функцию о низкоуровневой ошибке, программист должен написать промежуточный код, который передаст информацию об ошибке. Исключения автоматически распространяются за пределы области видимости до тех пор, пока они не будут перехвачены. ("Это не самое разумное — пытаться сделать из каждой функции брандмауэр". — [Stroustrup94, §16.8])

• Исключения выносят обработку ошибок и восстановление после них из основного потока управления. Проверка кода ошибки и ее обработка перемежается с основным потоком управления программы, тем самым запутывая его. Это затрудняет понимание и сопровождение как основного кода программы, так и кода обработки ошибок. Обработка исключений естественным образом перемещает обнаружение ошибок и восстановление после них в отдельные catch-блоки, т.е. делают обработку ошибок существенно более модульной. Тем самым основной код программы, как и код обработки ошибок, оказывается более понятным и легче сопровождаемым.

Перейти на страницу:
Прокомментировать
Подтвердите что вы не робот:*