straustrup2 (852740), страница 65
Текст из файла (страница 65)
Если функция перечисляетсвои особые ситуации, то она дает определенную гарантию всякой вызывающей ее функции, а именно,если попытается запустить иную особую ситуацию, то это приведет к вызову функции unexpected().Стандартное предназначение unexpected() состоит в вызове функции terminate(), которая, в своюочередь, обычно вызывает abort(). Подробности даны в $$9.7.По сути определениеvoid f() throw (x2, x3, x4){// какие-то операторы}эквивалентно такому определениюvoid f(){try {// какие-то операторы}catch (x2) { // повторный запускthrow;}catch (x3) { // повторный запускthrow;}catch (x4) { // повторный запускthrow;}catch (...) {unexpected();}}Преимущество явного задания особых ситуаций функции в ее описании перед эквивалентнымспособом, когда происходит проверка на особые ситуации в теле функции, не только в более краткойзаписи.
Главное здесь в том, что описание функции входит в ее интерфейс, который видим для всехвызывающих функций. С другой стороны, определение функции может и не быть универсальнодоступным. Даже если у вас есть исходные тексты всех библиотечных функций, обычно желаниеизучать их возникает не часто.Если в описании функции не указаны ее особые ситуации, считается, что она может запустить любуюособую ситуацию.int f();// может запустить любую особую ситуациюЕсли функция не будет запускать никаких особых ситуаций, ее можно описать, явно указав пустойсписок:int g() throw ();// не запускает никаких особых ситуацийКазалось было бы логично, чтобы по умолчанию функция не запускала никаких особых ситуаций. Нотогда пришлось бы описывать свои особые ситуации практически для каждой функции Это, как правило,251Бьерн Страуструп.Язык программирования С++требовало бы ее перетрансляции, а кроме того препятствовало бы общению с функциями,написанными на других языках.
В результате программист стал бы стремиться отключить механизмособых ситуаций и писал бы излишние операторы, чтобы обойти их. Пользователь считал бы такиепрограммы надежными, поскольку мог не заметить подмены, но это было бы совершенно неоправдано.9.6.1 Неожиданные особые ситуацииЕсли к описанию особых ситуаций относиться не достаточно серьезно, то результатом может бытьвызов unexpected(), что нежелательно во всех случая, кроме отладки. Избежать вызова unexpected()можно, если хорошо организовать структуру особых ситуации и описание интерфейса. С другойстороны, вызов unexpected() можно перехватить и сделать его безвредным.Если компонент Y хорошо разработан, все его особые ситуации могут быть только производнымиодного класса, скажем Yerr.
Поэтому, если есть описаниеclass someYerr : public Yerr { /* ... */ };то функция, описанная какvoid f() throw (Xerr, Yerr, IOerr);будет передавать любую особую ситуацию типа Yerr вызывающей функции. В частности, обработкаособой ситуации типа someYerr в f() сведется к передаче ее вызывающей f() функции.Бывает случаи, когда окончание программы при появлении неожиданной особой ситуации являетсяслишком строгим решением. Допустим функция g() написана для несетевого режима в распределеннойсистеме. Естественно, в g() ничего неизвестно об особых ситуациях, связанных с сетью, поэтому припоявлении любой из них вызывается unexpected(). Значит для использования g() в распределеннойсистеме нужно предоставить обработчик сетевых особых ситуаций или переписать g(). Если допустить,что переписать g() невозможно или нежелательно, проблему можно решить, переопределив действиефункции unexpected(). Для этого служит функция set_unexpected().
Вначале мы определим класс,который позволит нам применить для функций unexpected() метод "запроса ресурсов путеминициализации" :typedef void(*PFV)();PFV set_unexpected(PFV);class STC {// класс для сохранения и восстановленияPFV old;// функций unexpected()public:STC(PFV f) { old = set_unexpected(f); }~STC() { set_unexpected(old); }};Теперь мы определим функцию, которая должна в нашем примере заменить unexpected():void rethrow() { throw; }// перезапуск всех сетевых// особых ситуацийНаконец, можно дать вариант функции g(), предназначенный для работы в сетевом режиме:void networked_g(){STC xx(&rethrow); // теперь unexpected() вызывает rethrow()g();}В предыдущем разделе было показано, что unexpected() потенциально вызывается из обработчикаcatch (...).
Значит в нашем случае обязательно произойдет повторный запуск особой ситуации.Повторный запуск, когда особая ситуация не запускалась, приводит к вызову terminate(). Посколькуобработчик catch (...) находится вне той области видимости, в которой была запущена сетевая особаяситуация, бесконечный цикл возникнуть не может.Есть еще одно, довольно опасное, решение, когда на неожиданную особую ситуацию просто252Бьерн Страуструп.Язык программирования С++"закрывают глаза":void muddle_on() { cerr << "не замечаем особой ситуации\n"; }// ...STC xx(&muddle_on);// теперь действие unexpected() сводится// просто к печати сообщенияТакое переопределение действия unexpected() позволяет нормально вернуться из функции,обнаружившей неожиданную особую ситуацию.
Несмотря на свою очевидную опасность, это решениеиспользуется. Например, можно "закрыть глаза" на особые ситуации в одной части системы иотлаживать другие ее части. Такой подход может быть полезен в процессе отладки и развития системы,перенесенной с языка программирования без особых ситуаций. Все-таки, как правило лучше, еслиошибки проявляются как можно раньше.Возможно другое решение, когда вызов unexpected() преобразуется в запуск особой ситуации Fail(неудача):void fail() { throw Fail; }// ...STC yy(&fail);При таком решении вызывающая функция не должна подробно разбираться в возможном результатевызываемой функции: эта функции завершится либо успешно (т.е.
возвратится нормально), либонеудачно (т.е. запустит Fail). Очевидный недостаток этого решения в том, что не учитываетсядополнительная информация, которая может сопровождать особую ситуацию. Впрочем, принеобходимости ее можно учесть, если передавать информацию вместе с Fail.9.7 Неперехваченные особые ситуацииЕсли особая ситуация запущена и не перехвачена, то вызывается функция terminate(). Она жевызывается, когда система поддержки особых ситуаций обнаруживает, что структура стека нарушена,или когда в процессе обработки особой ситуации при раскручивании стека вызывается деструктор, и онпытается завершить свою работу, запустив особую ситуацию.Действие terminate() сводится к выполнению самой последней функции, заданной как параметр дляset_terminate():typedef void (*PFV)();PFV set_terminate(PFV);Функция set_terminate() возвращает указатель на ту функцию, которая была задана как параметр впредыдущем обращении к ней.Необходимость такой функции как terminate() объясняется тем, что иногда вместо механизма особыхситуаций требуются более грубые приемы.
Например, terminate() можно использовать для прекращенияпроцесса, а, возможно, и для повторного запуска системы. Эта функция служит экстренным средством,которое применяется, когда отказала стратегия обработки ошибок, рассчитанная на особые ситуации, исамое время применить стратегию более низкого уровня.Функция unexpected() используется в сходных, но не столь серьезных случаях, а именно, когда функциязапустила особую ситуацию, не указанную в ее описании. Действие функции unexpected() сводится квыполнению самой последней функции, заданной как параметр для функции set_unexpected().По умолчанию unexpected() вызывает terminate(), а та, в свою очередь, вызывает функцию abort().Предполагается, что такое соглашение устроит большинство пользователей.Предполагается, что функция terminate() не возвращается в обратившеюся ней функцию.Напомним, что вызов abort() свидетельствует о ненормальном завершении программы.
Длянормального выхода из программы используется функция exit(). Она возвращает значение, котороепоказывает окружающей системе насколько корректно закончилась программа.253Бьерн Страуструп.Язык программирования С++9.8 Другие способы обработки ошибокМеханизм особых ситуаций нужен для того, чтобы из одной части программы можно было сообщить вдругую о возникновении в первой "особой ситуации".
При этом предполагается, что части программынаписаны независимо друг от друга, и в той части, которая обрабатывает особую ситуацию, возможнаосмысленная реакция на ошибку.Как же должен быть устроен обработчик особой ситуации? Приведем несколько вариантов:int f(int arg){try {g(arg);}catch (x1) {// исправить ошибку и повторитьg(arg);}catch (x2) {// произвести вычисления и вернуть результатreturn 2;}catch (x3) {// передать ошибкуthrow;}catch (x4) {// вместо x4 запустить другую особую ситуациюthrow xxii;}catch (x5) {// исправить ошибку и продолжить со следующего оператора}catch (...) {// отказ от обработки ошибкиterminate();}// ...}Укажем, что в обработчике доступны переменные из области видимости, содержащей проверяемыйблок этого обработчика. Переменные, описанные в других обработчиках или других проверяемыхблоках, конечно, недоступны:void f(){int i1;// ...try {int i2;// ...}catch (x1) {int i3;// ...}catch (x4) {i1 = 1;i2 = 2;i3 = 3;}// нормально// ошибка: i2 здесь невидимо// ошибка: i3 здесь невидимо254Бьерн Страуструп.Язык программирования С++}Нужна общая стратегия для эффективного использования обработчиков в программе.
Все компонентыпрограммы должны согласованно использовать особые ситуации и иметь общую часть для обработкиошибок. Механизм обработки особых ситуаций является нелокальным по своей сути, поэтому так важнопридерживаться общей стратегии. Это предполагает, что стратегия обработки ошибок должнаразрабатываться на самых ранних стадиях проектах. Кроме того, эта стратегия должна быть простой(по сравнению со сложностью всей программы) и ясной. Последовательно проводить сложнуюстратегию в такой сложной по своей природе области программирования, как восстановление послеошибок, будет просто невозможно.Прежде всего стоит сразу отказаться от того, что одно средство или один прием можно применять дляобработки всех ошибок.