Б. Страуструп - Язык программирования С++ (1119446), страница 67
Текст из файла (страница 67)
С другойстороны, особые ситуации можно рассматривать просто как еще одну структуру управления.Подтвердим это примером:class message { /* ... */ };class queue {// ...message* get();// ...};// сообщение// очередь// вернуть 0, если очередь пустаvoid f1(queue& q){message* m = q.get();if (m == 0) { // очередь пуста// ...}// используем m}Этот пример можно записать так:class Empty { } // тип особой ситуации "Пустая_очередь"class queue {// ...message* get(); // запустить Empty, если очередь пуста// ...};void f2(queue& q){try {message* m = q.get();// используем m}catch (Empty) { // очередь пуста// ...}}В варианте с особой ситуацией есть даже какая-то прелесть.
Это хороший пример того, когда трудносказать, можно ли считать такую ситуацию ошибкой. Если очередь не должна быть пустой (т.е. онабывает пустой очень редко, скажем один раз из тысячи), и действия в случае пустой очереди можнорассматривать как восстановление, то в функции f2() взгляд на особую ситуацию будет такой, которогомы до сих пор и придерживались (т.е. обработка особых ситуаций есть обработка ошибок). Еслиочередь часто бывает пустой, а принимаемые в этом случае действия образуют одну из ветвейнормального хода программы, то придется отказаться от такого взгляда на особую ситуацию, афункцию f2() надо переписать:class queue {// ...message* get();int empty();// ...};// запустить Empty, если очередь пуста249Бьерн Страуструп.Язык программирования С++void f3(queue& q){if (q.empty()) { // очередь пуста// ...}else {message* m = q.get();// используем m}}Отметим, что вынести из функции get() проверку очереди на пустоту можно только при условии, что кочереди нет параллельных обращений.Не так то просто отказаться от взгляда, что обработка особой ситуации есть обработка ошибки.
Покамы придерживаемся такой точки зрения, программа четко подразделяется на две части: обычная частьи часть обработки ошибок. Такая программа более понятна. К сожалению, в реальных задачах провестичеткое разделение невозможно, поэтому структура программы должна (и будет) отражать этот факт.Допустим, очередь бывает пустой только один раз (так может быть, если функция get() используется вцикле, и пустота очереди говорит о конце цикла). Тогда пустота очереди не является чем-то страннымили ошибочным. Поэтому, используя для обозначения конца очереди особую ситуацию, мы расширяемпредставление об особых ситуациях как ошибках. С другой стороны, действия, принимаемые в случаепустой очереди, явно отличаются от действий, принимаемых в ходе цикла (т.е. в обычном случае).Механизм особых ситуаций является менее структурированным, чем такие локальные структурыуправления как операторы if или for.
Обычно он к тому же является не столь эффективным, если особаяситуация действительно возникла. Поэтому особые ситуации следует использовать только в томслучае, когда нет хорошего решения с более традиционными управляющими структурами, или оно,вообще, невозможно. Например, в случае пустой очереди можно прекрасно использовать длясигнализации об этом значение, а именно нулевое значение указателя на строку message, значитособая ситуация здесь не нужна. Однако, если бы из класса queue мы получали вместо указателязначение типа int, то то могло не найтись такого значения, обозначающего пустую очередь.
В такомслучае функция get() становится эквивалентной операции индексации из $$9.1, и более привлекательнопредставлять пустую очередь с помощью особой ситуации. Последнее соображение подсказывает, чтов самом общем шаблоне типа для очереди придется для обозначения пустой очереди использоватьособую ситуацию, а работающая с очередью функция будет такой:void f(Queue<X>& q){try {for (;;) {// ``бесконечный цикл''// прерываемый особой ситуациейX m = q.get();// ...}}catch (Queue<X>::Empty) {return;}}Если приведенный цикл выполняется тысячи раз, то он, по всей видимости, будет более эффективным,чем обычный цикл с проверкой условия пустоты очереди. Если же он выполняется только несколькораз, то обычный цикл почти наверняка эффективней.В очереди общего вида особая ситуация используется как способ возврата из функции get().Использование особых ситуаций как способа возврата может быть элегантным способом завершенияфункций поиска.
Особенно это подходит для рекурсивных функций поиска в дереве. Однако, применяяособые ситуации для таких целей, легко перейти грань разумного и получить маловразумительнуюпрограмму. Все-таки всюду, где это действительно оправдано, надо придерживаться той точки зрения,что обработка особой ситуации есть обработка ошибки. Обработка ошибок по самой своей природе250Бьерн Страуструп.Язык программирования С++занятие сложное, поэтому ценность имеют любые методы, которые дают ясное представление ошибокв языке и способ их обработки.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 ();// не запускает никаких особых ситуацийКазалось было бы логично, чтобы по умолчанию функция не запускала никаких особых ситуаций. Нотогда пришлось бы описывать свои особые ситуации практически для каждой функции Это, как правило,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().