straustrup2 (852740), страница 61
Текст из файла (страница 61)
Обсуждение функций реакций и особых ситуацией будет продолжено в $$9.4.3.Механизм особых ситуаций успешно заменяет традиционные способы обработки ошибок в тех случаях,когда последние являются неполным, некрасивым или чреватым ошибками решением. Этот механизмпозволяет явно отделить часть программы, в которой обрабатываются ошибки, от остальной ее части,тем самым программа становится более понятной и с ней проще работать различным сервиснымпрограммам. Свойственный этому механизму регулярный способ обработки ошибок упрощаетвзаимодействие между раздельно написанными частями программы.В этом способе обработки ошибок есть для программирующих на С новый момент: стандартнаяреакция на ошибку (особенно на ошибку в библиотечной функции) состоит в завершении программы.Традиционной была реакция продолжать программу в надежде, что она как-то завершится сама.Поэтому способ, базирующийся на особых ситуациях, делает программу более "хрупкой" в том смысле,что требуется больше усилий и внимания для ее нормального выполнения.
Но это все-таки лучше, чемполучать неверные результаты на более поздней стадии развития программы (или получать их ещепозже, когда программу сочтут завершенной и передадут ничего не подозревающему пользователю).Если завершение программы является неприемлемой реакцией, можно смоделировать традиционнуюреакцию с помощью перехвата всех особых ситуаций или всех особых ситуаций, принадлежащихспециальному классу ($$9.3.2).Механизм особых ситуаций можно рассматривать как динамический аналог механизма контроля типов ипроверки неоднозначности на стадии трансляции.
При таком подходе более важной становится стадияпроектирования программы, и требуется большая поддержка процесса выполнения программы, чемдля программ на С. Однако, в результате получится более предсказуемая программа, ее будет прощевстроить в программную систему, она будет понятнее другим программистам и с ней проще будетработать различным сервисным программам. Можно сказать, что механизм особых ситуацийподдерживает, подобно другим средствам С++, "хороший" стиль программирования, который в такихязыках, как С, можно применять только не в полном объеме и на неформальном уровне.234Бьерн Страуструп.Язык программирования С++Все же надо сознавать, что обработка ошибок остается трудной задачей, и, хотя механизм особыхситуаций более строгий, чем традиционные способы, он все равно недостаточно структурирован посравнению с конструкциями, допускающими только локальную передачу управления.9.1.2 Другие точки зрения на особые ситуации"Особая ситуация" - одно из тех понятий, которые имеют разный смысл для разных людей.
В С++механизм особых ситуаций предназначен для обработки ошибок. В частности, он предназначен дляобработки ошибок в программах, состоящих из независимо создаваемых компонентов.Этот механизм рассчитан на особые ситуации, возникающие только при последовательном выполнениипрограммы (например, контроль границ массива). Асинхронные особые ситуации такие, например, какпрерывания от клавиатуры, нельзя непосредственно обрабатывать с помощью этого механизма. Вразличных системах существуют другие механизмы, например, сигналы, но они здесь нерассматриваются, поскольку зависят от конкретной системы.Механизм особых ситуаций является конструкцией с нелокальной передачей управления и его можнорассматривать как вариант оператора return. Поэтому особые ситуации можно использовать для целей,никак не связанных с обработкой ошибок ($$9.5). Все-таки основным назначением механизма особыхситуаций и темой этой главы будет обработка ошибок и создание устойчивых к ошибкам программ.9.2 Различение особых ситуацийЕстественно, в программе возможны несколько различных динамических ошибок.
Эти ошибки можносопоставить с особыми ситуациями, имеющими различные имена. Так, в классе Vector обычноприходится выявлять и сообщать об ошибках двух видов: ошибки диапазона и ошибки, вызванныенеподходящим для конструктора параметром:class Vector {int* p;int sz;public:enum { max = 32000 };class Range { }; // особая ситуация индексаclass Size { };// особая ситуация "неверный размер"Vector(int sz);int& operator[](int i);// ...};Как было сказано, операция индексации запускает особую ситуацию Range, если ей задан выходящийиз диапазона значений индекс.
Конструктор запускает особую ситуацию Size, если ему заданнедопустимый размер вектора:Vector::Vector(int sz){if (sz<0 || max<sz) throw Size();// ...}Пользователь класса Vector может различить эти две особые ситуации, если в проверяемом блоке (т.е. вблоке оператора try) укажет обработчики для обеих ситуаций:void f(){try {use_vectors();}catch (Vector::Range) {// ...}catch (Vector::Size) {235Бьерн Страуструп.Язык программирования С++// ...}}В зависимости от особой ситуации будет выполняться соответствующий обработчик.
Если управлениедойдет до конца операторов обработчика, следующим будет выполняться оператор, который идетпосле списка обработчиков:void f(){try {use_vectors();}catch (Vector::Range) {// исправить индекс и// попробовать опять:f();}catch (Vector::Size) {cerr << "Ошибка в конструкторе Vector::Size";exit(99);}// сюда мы попадем, если вообще не было особых ситуаций// или после обработки особой ситуации Range}Список обработчиков напоминает переключатель, но здесь в теле обработчика операторы break ненужны.
Синтаксис списка обработчиков отличен от синтаксиса вариантов case переключателя частичнопо этой причине, частично потому, чтобы показать, что каждый обработчик определяет свою областьвидимости (см. $$9.8).Не обязательно все особые ситуации перехватывать в одной функции:void f1(){try {f2(v);}catch (Vector::Size) {// ...}}void f2(Vector& v){try {use_vectors();}catch (Vector::Range) {// ...}}Здесь f2() перехватит особую ситуацию Range, возникающую в use_vectors(), а особая ситуация Sizeбудет оставлена для f1().С точки зрения языка особая ситуация считается обработанной сразу при входе в тело ее обработчика.Поэтому все особые ситуации, запускаемые при выполнении этого обработчика, должныобрабатываться в функциях, вызвавших ту функцию, которая содержит проверяемый блок.
Значит вследующем примере не возникнет бесконечного цикла:try {// ...236Бьерн Страуструп.Язык программирования С++}catch (input_overflow) {// ...throw input_overflow();}Здесь input_overflow (переполнение при вводе) - имя глобального класса.Обработчики особых ситуаций могут быть вложенными:try {// ...}catch (xxii) {try {// сложная реакция}catch (xxii) {// ошибка в процессе сложной реакции}}Однако, такая вложенность редко бывает нужна в обычных программах, и чаще всего она являетсясвидетельством плохого стиля.9.3 Имена особых ситуацийОсобая ситуация перехватывается благодаря своему типу. Однако, запускается ведь не тип, а объект.Если нам нужно передать некоторую информацию из точки запуска в обработчик, то для этого ееследует поместить в запускаемый объект.
Например, допустим нужно знать значение индекса,выходящее за границы диапазона:class Vector {public:class Range {public:int index;Range(int i) : index(i) { }};// ...int& operator[](int i)};int Vector::operator[](int i){if (o<=i && i <sz) return p[i];throw Range(i);}Чтобы исследовать недопустимое значение индекса, в обработчике нужно дать имя объекту,представляющему особую ситуацию:void f(Vector& v){// ...try {do_something(v);}catch (Vector::Range r ) {cerr << "недопустимый индекс" << r.index << '\n';// ...}237Бьерн Страуструп.Язык программирования С++// ...}Конструкция в скобках после служебного слова catch является по сути описанием и она аналогичнаописанию формального параметра функции. В ней указывается каким может быть тип параметра (т.е.особой ситуации) и может задаваться имя для фактической, т.е. запущенной, особой ситуации.Вспомним, что в шаблонах типов у нас был выбор для именования особых ситуаций.
В каждомсозданном по шаблону классе был свой класс особой ситуации:template<class T> class Allocator {// ...class Exhausted { }// ...T* get();};void f(Allocator<int>& ai, Allocator<double>& ad){try {// ...}catch (Allocator<int>::Exhausted) {// ...}catch (Allocator<double>::Exhausted) {// ...}}С другой стороны, особая ситуация может быть общей для всех созданных по шаблону классов:class Allocator_Exhausted { };template<class T> class Allocator {// ...T* get();};void f(Allocator<int>& ai, Allocator<double>& ad){try {// ...}catch (Allocator_Exhausted) {// ...}}Какой способ задания особой ситуации предпочтительней, сказать трудно. Выбор зависит отназначения рассматриваемого шаблона.9.3.1 Группирование особых ситуацийОсобые ситуации естественным образом разбиваются на семейства.
Действительно, логичнопредставлять семейство Matherr, в которое входят Overflow (переполнение), Underflow (потерязначимости) и некоторые другие особые ситуации. Семейство Matherr образуют особые ситуации,которые могут запускать математические функции стандартной библиотеки.Один из способов задания такого семейства сводится к определению Matherr как типа, возможныезначения которого включают Overflow и все остальные:enum { Overflow, Underflow, Zerodivide, /* ... */ };238Бьерн Страуструп.Язык программирования С++try {// ...}catch (Matherr m) {switch (m) {case Overflow:// ...case Underflow:// ...// ...}// ...}Другой способ предполагает использование наследования и виртуальных функций, чтобы не вводитьпереключателя по значению поля типа.