Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
КРАТКИЙ ОБЗОР С.doc
Скачиваний:
1
Добавлен:
26.10.2018
Размер:
2.11 Mб
Скачать

9.6 Задание интерфейса

      Запуск или перехват особой ситуации отражается на взаимоотношениях       функций. Поэтому имеет смысл задавать в описании функции множество       особых ситуаций, которые она может запустить:       void f(int a) throw (x2, x3, x4);       В этом описании указано, что f() может запустить особые ситуации       x2, x3 и x4, а также ситуации всех производных от них типов, но       больше никакие ситуации она не запускает. Если функция перечисляет свои       особые ситуации, то она дает определенную гарантию всякой вызывающей ее       функции, а именно, если попытается запустить иную особую ситуацию, то       это приведет к вызову функции 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 (); // не запускает никаких особых ситуаций       Казалось было бы логично, чтобы по умолчанию функция не запускала       никаких особых ситуаций. Но тогда пришлось бы описывать свои особые       ситуации практически для каждой функции Это, как правило,       требовало бы ее перетрансляции, а кроме того препятствовало бы общению       с функциями, написанными на других языках. В результате программист       стал бы стремиться отключить механизм особых ситуаций и писал бы       излишние операторы, чтобы обойти их. Пользователь считал бы такие       программы надежными, поскольку мог не заметить подмены, но это было       бы совершенно неоправдано.

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 (...) находится вне той       области видимости, в которой была запущена сетевая особая ситуация,       бесконечный цикл возникнуть не может.       Есть еще одно, довольно опасное, решение, когда на неожиданную       особую ситуацию просто "закрывают глаза":       void muddle_on() { cerr << "не замечаем особой ситуации\n"; }       // ...       STC xx(&muddle_on); // теперь действие unexpected() сводится       // просто к печати сообщения       Такое переопределение действия unexpected() позволяет нормально       вернуться из функции, обнаружившей неожиданную особую ситуацию.       Несмотря на свою очевидную опасность, это решение используется.       Например, можно "закрыть глаза" на особые ситуации в одной части       системы и отлаживать другие ее части. Такой подход может быть       полезен в процессе отладки и развития системы, перенесенной с языка       программирования без особых ситуаций. Все-таки, как правило лучше,       если ошибки проявляются как можно раньше.       Возможно другое решение, когда вызов unexpected() преобразуется       в запуск особой ситуации Fail (неудача):       void fail() { throw Fail; }       // ...       STC yy(&fail);       При таком решении вызывающая функция не должна подробно разбираться в       возможном результате вызываемой функции: эта функции завершится       либо успешно (т.е. возвратится нормально), либо неудачно (т.е.       запустит Fail). Очевидный недостаток этого решения в том, что       не учитывается дополнительная информация, которая может сопровождать       особую ситуацию. Впрочем, при необходимости ее можно учесть, если       передавать информацию вместе с Fail.