Б. Страуструп - Язык программирования С++ (1119446), страница 64
Текст из файла (страница 64)
вблоке оператора 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:// ...// ...}// ...}Другой способ предполагает использование наследования и виртуальных функций, чтобы не вводитьпереключателя по значению поля типа.
Наследование помогает описать семейства особых ситуаций:class Matherr { };class Overflow: public Matherr { };class Underflow: public Matherr { };class Zerodivide: public Matherr { };// ...Часто бывает так, что нужно обработать особую ситуацию Matherr не зависимо от того, какая именноситуация из этого семейства произошла. Наследование позволяет сделать это просто:try {// ...}catch (Overflow) {// обработка Overflow или любой производной ситуации}catch (Matherr) {// обработка любой отличной от Overflow ситуации}В этом примере Overflow разбирается отдельно, а все другие особые ситуации из Matherr разбираютсякак один общий случай. Конечно, функция, содержащая catch (Matherr), не будет знать какую именноособую ситуацию она перехватывает.
Но какой бы она ни была, при входе в обработчик передаваемаяее копия будет Matherr. Обычно это как раз то, что нужно. Если это не так, особую ситуацию можноперехватить по ссылке (см. $$9.3.2).Иерархическое упорядочивание особых ситуаций может играть важную роль для создания яснойструктуры программы. Действительно, пусть такое упорядочивание отсутствует, и нужно обработать всеособые ситуации стандартной библиотеки математических функций.
Для этого придется добесконечности перечислять все возможные особые ситуации:try {// ...}catch (Overflow) { /* ... */ }catch (Underflow) { /* ... */ }catch (Zerodivide) { /* ... */ }// ...Это не только утомительно, но и опасно, поскольку можно забыть какую-нибудь особую ситуацию.Кроме того, необходимость перечислить в проверяемом блоке все особые ситуации практическигарантирует, что, когда семейство особых ситуаций библиотеки расширится, в программе пользователявозникнет ошибка. Это значит, что при введении новой особой ситуации в библиотеки математическихфункций придется перетранслировать все части программы, которые содержат обработчики всехособых ситуаций из Matherr. В общем случае такая перетрансляция неприемлема.
Часто даже нетвозможности найти все требующие перетрансляции части программы. Если такая возможность есть,239Бьерн Страуструп.Язык программирования С++нельзя требовать, чтобы всегда был доступен исходной текст любой части большой программы, иличтобы у нас были права изменять любую часть большой программы, исходный текст которой мы имеем.На самом деле, пользователь не должен думать о внутреннем устройстве библиотек. Все эти проблемыперетрансляции и сопровождения могут привести к тому, что после создания первой версии библиотекибудет нельзя вводить в ней новые особые ситуации. Но такое решение не подходит практически длявсех библиотек.Все эти доводы говорят за то, что особые ситуации нужно определять как иерархию классов (см. также$$9.6.1). Это, в свою очередь, означает, что особые ситуации могут быть членами нескольких групп:class network_file_err: public network_err,public file_system_err {// ...};// ошибки файловой системы в сети// ошибки сети// ошибки файловой системыОсобую ситуацию network_file_err можно перехватить в функциях, обрабатывающих особые ситуациисети:void f(){try {// какие-то операторы}catch (network_err) {// ...}}Ее также можно перехватить в функциях, обрабатывающих особые ситуации файловой системы:void g(){try {// какие-то другие операторы}catch (file_system_err) {// ...}}Это важный момент, поскольку такой системный сервис как работа в сети должен быть прозрачен, а этоозначает, что создатель функции g() может даже и не знать, что эта функция будет выполняться всетевом режиме.Отметим, что в настоящее время нет стандартного множества особых ситуаций для стандартнойматематической библиотеки и библиотеки ввода-вывода.
Задача комитетов ANSI и ISO постандартизации С++ решить нужно ли такое множество и какие в нем следует использовать имена иклассы.Поскольку можно сразу перехватить все особые ситуации (см. $$9.3.2), нет настоятельнойнеобходимости создавать для этой цели общий, базовый для всех особых ситуаций, класс. Однако,если все особые ситуации являются производными от пустого класса Exception (особая ситуация), то винтерфейсах их использование становится более регулярным (см.
$$9.6). Если вы используете общийбазовый класс Exception, убедитесь, что в нем ничего нет кроме виртуального деструктора. В противномслучае такой класс может вступить в противоречие с предполагаемым стандартом.9.3.2 Производные особые ситуацииЕсли для обработки особых ситуаций мы используем иерархию классов, то, естественно, каждыйобработчик должен разбираться только с частью информации, передаваемой при особых ситуациях.Можно сказать, что, как правило, особая ситуация перехватывается обработчиком ее базового класса, ане обработчиком класса, соответствующего именно этой особой ситуации. Именование и перехват240Бьерн Страуструп.Язык программирования С++обработчиком особой ситуации семантически эквивалентно именованию и получению параметра вфункции.
Проще говоря, формальный параметр инициализируется значением фактического параметра.Это означает, что запущенная особая ситуация "низводится" до особой ситуации, ожидаемойобработчиком. Например:class Matherr {// ...virtual void debug_print();};class Int_overflow : public Matherr {public:char* op;int opr1, opr2;;int_overflow(const char* p, int a, int b){ cerr << op << '(' << opr1 << ',' << opr2 << ')'; }};void f(){try {g();}catch (Matherr m) {// ...}}При входе в обработчик Matherr особая ситуация m является объектом Matherr, даже если приобращении к g() была запущена Int_overflow. Это означает, что дополнительная информация,передаваемая в Int_overflow, недоступна.Как обычно, чтобы иметь доступ к дополнительной информации можно использовать указатели илиссылки.