straustrup2 (852740), страница 62
Текст из файла (страница 62)
Наследование помогает описать семейства особых ситуаций: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, недоступна.Как обычно, чтобы иметь доступ к дополнительной информации можно использовать указатели илиссылки. Поэтому можно было написать так:int add(int x, int y)// сложить x и y с контролем{if (x > 0 && y > 0 && x > MAXINT - y|| x < 0 && y < 0 && x < MININT + y)throw Int_overflow("+", x, y);// Сюда мы попадаем, либо когда проверка// на переполнение дала отрицательный результат,// либо когда x и y имеют разные знакиreturn x + y;}void f(){try {add(1,2);add(MAXINT,-2);add(MAXINT,2);}catch (Matherr& m) {// ...m.debug_print();}}// а дальше - переполнениеЗдесь последнее обращение к add приведет к запуску особой ситуации, который, в свою очередь,приведет к вызову Int_overflow::debug_print().Если бы особая ситуация передавалась по значению, а не241Бьерн Страуструп.Язык программирования С++по ссылке, то была бы вызвана функция Matherr::debug_print().Нередко бывает так, что перехватив особую ситуацию, обработчик решает, что с этой ошибкой онничего не сможет поделать.
В таком случае самое естественное запустить особую ситуацию снова внадежде, что с ней сумеет разобраться другой обработчик:void h(){try {// какие-то операторы}catch (Matherr) {if (can_handle_it) {// сделать ее}else {throw;// особой ситуации}}}// если обработка возможна,// повторный запуск перехваченнойПовторный запуск записывается как оператор throw без параметров.
При этом снова запускаетсяисходная особая ситуация, которая была перехвачена, а не та ее часть, на которую рассчитанобработчик Matherr. Иными словами, если была запущена Int_overflow, вызывающая h() функция моглабы перехватить ее как Int_overflow, несмотря на то, что она была перехвачена в h() как Matherr изапущена снова:void k(){try {h();// ...}catch (Int_overflow) {// ...}}Полезен вырожденный случай перезапуска. Как и для функций, эллипсис ... для обработчика означает"любой параметр", поэтому оператор catch (...) означает перехват любой особой ситуации:void m(){try {// какие-то операторы}catch (...) {// привести все в порядокthrow;}}Этот пример надо понимать так: если при выполнении основной части m() возникает особая ситуация,выполняется обработчик, которые выполняет общие действия по устранению последствий особойситуации, после этих действий особая ситуация, вызвавшая их, запускается повторно.Поскольку обработчик может перехватить производные особые ситуации нескольких типов, порядок, вкотором идут обработчики в проверяемом блоке, существенен.
Обработчики пытаются перехватитьособую ситуацию в порядке их описания. Приведем пример:try {// ...242Бьерн Страуструп.}catch (ibuf) {// обработка}catch (io) {// обработка}catch (stdlib)// обработка}catch (...) {// обработка}Язык программирования С++переполнения буфера вводалюбой ошибки ввода-вывода{любой особой ситуации в библиотекевсех остальных особых ситуацийТип особой ситуации в обработчике соответствует типу запущенной особой ситуации в следующихслучаях: если эти типы совпадают, или второй тип является типом доступного базового классазапущенной ситуации, или он является указателем на такой класс, а тип ожидаемой ситуации тожеуказатель ($$R.4.6).Поскольку транслятору известна иерархия классов, он способен обнаружить такие нелепые ошибки,когда обработчик catch (...) указан не последним, или когда обработчик ситуации базового классапредшествует обработчику производной от этого класса ситуации ($$R15.4).
В обоих случая,последующий обработчик (или обработчики) не могут быть запущены, поскольку они "маскируются"первым обработчиком.9.4 Запросы ресурсовЕсли в некоторой функции потребуются определенные ресурсы, например, нужно открыть файл,отвести блок памяти в области свободной памяти, установить монопольные права доступа и т.д., длядальнейшей работы системы обычно бывает крайне важно, чтобы ресурсы были освобожденынадлежащим образом. Обычно такой "надлежащий способ" реализует функция, в которой происходитзапрос ресурсов и освобождение их перед выходом. Например:void use_file(const char* fn){FILE* f = fopen(fn,"w");fclose(f);}// работаем с fВсе это выглядит вполне нормально до тех пор, пока вы не поймете, что при любой ошибке,происшедшей после вызова fopen() и до вызова fclose(), возникнет особая ситуация, в результатекоторой мы выйдем из use_file(), не обращаясь к fclose().