Программирование >>  Разработка устойчивых систем 

1 ... 9 10 11 [ 12 ] 13 14 15 ... 196


Программирование с учетом исключений 57

Начинайте со стандартных исключений

Прежде чем создавать собственные исключения, ознакомьтесь с исключениями стандартной библиотеки С++. Если стандартное исгслючение делает то, что вам нужно, скорее всего, будет проще и удобнее работать с ним.

Если нужный тип исключения отсутствует в стандартной библиотеке, попробуйте определить его наследованием от одного из существующих исключений. Хорошо, если ваши пользователи в своих программах всегда смогут рассчитывать на доступность функции what(), определенной в интерфейсе класса exception.

Вложение специализированных исключений

Если вы создаете исключения для своего конкретного loiacca, желательно вложить классы исключений либо внутрь luiacca, либо внутрь пространства имен, содержащего класс. Тем самым вы четко сообщаете читателю программы, что исключение используется только вашим классом. Кроме того, вложенные исключения не загромождают глобальное пространство имен.

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

Иерархии исключений

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

Стандартные исключения С++ дают хороший пример иерархии исключений. Если удастся, попробуйте создать свои исютючения на их базе.

Множественное наследование

Как будет показано в главе 9, необходимость во множественном наследовании возникает лишь тогда, когда указатель на объект требуется повышать до двух разных базовых классов (то есть вам требуется полиморфное поведение в отношении обоих базовых классов). Оказывается, применение множественного наследования в иерархиях исключений вполне уместно, потому что исключение может быть обработчиком любого из корней иерархии множественного наследования.

Перехват по ссылке

Как было показано в разделе Поиск подходящего обработчика , исключения стоит перехватывать по ссылке, а не по значению, по двум причинам:

чтобы избежать ненужного копирования объекта исключений при передаче обработчику;

чтобы предотвратить усечение объекта при перехвате производного исключения как объекта базового класса.

Хотя вы также можете запускать и перехватывать указатели, это лишь уменьшает свободу действий: обе стороны (запускающая и перехватывающая) должны



Ознакомьтесь с умными указателями по адресу http: www.boost.org/libs/smart ptr/index.htm. Некоторые из них рекомендованы к включению в следующую версию стандартного языка С++.

согласовать процедуры выделения и освобождения памяти. Это может вызвать проблемы, поскольку само исключение могло произойти из-за нехватки свободной памяти в куче. При запуске объектов-исключений система обработки исключений берет все хлопоты с памятью на себя.

Запуск исключений в конструкторах

Так как конструктор не имеет возвращаемого значения, раньше об ошибках конструирования можно было сообщить двумя способами:

установить нелокальный флаг и надеяться, что пользователь проверит его;

вернуть частично созданный объект и надеяться, что пользователь проверит его.

Возникает серьезная проблема: программисты С склонны полагать, что объекты всегда создаются успешно. В С такие ожидания вполне оправданны благодаря примитивности типов. Но дальнейшее выполнение после неудачного конструирования в С++ кончится катастрофой, поэтому конструкторы являются одним из основных кандидатов на запуск исключений - в вашем распоряжении появляется надежный, эффективный способ обработки ошибок конструирования. Однако при этом необходимо следить за указателями внутри объектов и за тем, как происходит освобождение ресурсов при запуске исключений в конструкторах.

Исключения в деструкторах запрещены

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

Если в деструкторе вызываются функции, которые могут запускать исключе-V. эти вызовы должны быть заключены в блок try внутри деструктора, и деструктор должен обработать все исключения сам. Ни одно ис1слючение не должно выйти за пределы деструктора.

Избегайте низкоуровневых указателей

Вернитесь к примеру Wrapped.срр, представленному ранее в этой главе. Низкоуровневый указатель, для которого в конструкторе выделяются ресурсы, обычно создает потенциальную угрозу утечки памяти. Указатель не имеет деструктора, поэтому ресурсы не освобождаются при возникновении исключения в конструкторе. В качестве указателей, ссылающихся на память в куче, лучше использовать указатель auto ptr или другие типы умных указателей.

Издержки обработки исключений

Запуск исключения сопряжен с существенными издержками (но это полезные затраты, потому что объекты зачищаются автоматически!). По этой причине исклю-



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

Borland разрешает исключения по умолчанию, а для их запрета используется ключ компилятора -х. Microsoft по умолчанию запрещает исключения, и они включаются ключом -GX. В обоих компиляторах ключ -с активизирует режим только компиляция (без сборки).

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

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

При вызове функции информация о ней заносится в стек в виде экземпляра активационной записи (Activation Record Instance, ARI), также называемый кадром стека. Типичный кадр стека состоит из адреса вызывающей функции (по которому возвращается управление), указателя на ARI статического родителя функции (области видимости, лексически содержащей вызванную функцию, для обращения к переменным, глобальным по отношению к функции), и указателя на вызвавшую функцию (динамическогородителя). Путь, образуемый ссылками на динамических родителей всех уровней, называется динамической цепочкой, или цепочкой вызовов (этот термин уже встречался ранее в этой главе). Именно этот механизм делает возможным возврат управления при запуске исключения, и он же позволяет разрабатывать независимые компоненты с возможностью обмена информацией об ошибках во время выполнения программы.

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

Для демонстрации следующая программа с поддержкой исключений и без нее была откомпилирована в Borland С++ Builder и Microsoft Visual С++:

: COl: HasDestructor.cpp {0} struct HasDestructor {



1 ... 9 10 11 [ 12 ] 13 14 15 ... 196

© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки.
Яндекс.Метрика