Чтение онлайн

ЖАНРЫ

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

Александреску Андрей

Шрифт:

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

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
– блоки, т.е. делают обработку ошибок существенно более модульной. Тем самым основной код программы, как и код обработки ошибок, оказывается более понятным и легче сопровождаемым.

• Исключения оказываются наилучшим способом уведомления об ошибках в конструкторах и операторах. Копирующие конструкторы и операторы имеют предопределенные сигнатуры, в которых просто нет места для кодов возврата. В частности, конструкторы вообще не имеют возвращаемого типа (даже

void
), а, например, каждый оператор
operator+
должен получать в точности два параметра и возвращать только один объект (предопределенного типа; см. рекомендацию 26). В случае операторов использование кодов ошибок, как минимум, возможно, если не желательно; для этого можно воспользоваться глобальной переменной наподобие
errno
или несколько более худшим методом внедрения кода состояния в состав объекта. Но для конструкторов использование кодов ошибок неприменимо, поскольку в языке С++ исключения в конструкторе и сбои в конструкторе настолько тесно связаны, что по сути являются синонимами. Если вы попытаетесь использовать подход с глобальной переменной наподобие

SomeType anObject; // Конструирование объекта

if (SomeType::ConstructionWasOk) { // Проверка результата

 // ... // конструирования

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

SomeType::ConstructionWasOk
в многопоточных приложениях (см. [Stroustrup00] §E.3.5).

Главный потенциальный недостаток обработки исключений заключается в том, что требует от программиста хороших знаний о некоторых идиомах, возникающих в результате вынесения исключений из основного потока управления. Например, деструкторы и функции освобождения ресурсов не должны генерировать исключения (см. рекомендацию 51), а промежуточный код должен быть корректен при наличии исключений (см. рекомендацию 71 и ссылки); распространенная идиома для достижения этого заключается в отдельном выполнении всей работы, которая может привести к генерации исключений, и только после успешного завершения результаты работы принимаются, и состояние программы модифицируется с использованием только тех операций, которые предоставляют гарантию бессбойности (см. рекомендацию 51 и [Sutter00] §9-10, §13). Однако использование кодов ошибок также имеет собственные идиомы. Просто эти идиомы более старые и их знает большее количество программистов — но при этом, к сожалению, зачастую их просто игнорирует...

Обычно использование обработки исключений не приводит к снижению производительности. Для начала заметим, что вы всегда должны включать поддержку обработки исключений в вашем компиляторе, даже если по умолчанию она отключена; в противном случае вы не сможете получить предусмотренное стандартом поведение и уведомление об ошибках от таких операций С++, как оператор new или операции стандартной библиотеки наподобие вставок в контейнер (см. раздел исключений в данной рекомендации).

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

Поделиться с друзьями: