С.Б. Липпман, Ж. Лажойе - Язык программирования С++ Вводный курс (1114944), страница 93
Текст из файла (страница 93)
Например:void (*pf3)( int, int ) throw(string) = &doit;539С++ для начинающихТретья инициализация не имеет смысла. Объявление указателя гарантирует, что pf3адресует функцию, которая может возбуждать только исключения типа string. Ноdoit() возбуждает также исключения типа exceptionType. Посколькуона неподходит под ограничения, накладываемые спецификацией исключений pf3, то не можетслужить корректным инициализатором для pf3, так что компилятор выдает ошибку.Упражнение 11.9В коде, разработанном для упражнения 11.8, измените объявление оператораoperator[]() в классе IntArray, добавив спецификацию возбуждаемых имисключений.
Модифицируйте программу так, чтобы operator[]() возбуждалисключение, не указанное в спецификации. Что при этом происходит?Упражнение 11.10Какие исключения может возбуждать функция, если ее спецификация исключений имеетвид throw()? А если у нее нет такой спецификации?Упражнение 11.11void example() throw(string);(a) void (*pf1)() = example;Какое из следующих присваиваний ошибочно? Почему?(b) void (*pf2) throw() = example;11.5.
Исключения и вопросы проектированияС обработкой исключений в программах C++ связано несколько вопросов. Хотяподдержка такой обработки встроена в язык, не стоит использовать ее везде. Обычно онаприменяется для обмена информацией об ошибках между независимо разработаннымичастями программы. Например, автор некоторой библиотеки может с помощьюисключений сообщать пользователям об ошибках. Если библиотечная функцияобнаруживает аномальную ситуацию, которую не способна обработать самостоятельно,она может возбудить исключение для уведомления вызывающей программы.В нашем примере в библиотеке определен класс iStack и его функции-члены.
Разумнопредположить, что программист, кодировавший main(), где используется этабиблиотека, не разрабатывал ее. Функции-члены класса iStack могут обнаружить, чтооперация pop() вызвана, когда стек пуст, или что операция push() вызвана, когда стекполон; однако разработчик библиотеки ничего не знал о программе, пользующейся егофункциями, так что не мог разрешить проблему локально. Не сумев обработать ошибкувнутри функций-членов, мы решили возбуждать исключения, чтобы известитьвызывающую программу.Хотя C++ поддерживает исключения, следует применять и другие методы обработкиошибок (например, возврат кода ошибки) – там, где это более уместно.
Однозначногоответа на вопрос: “Когда ошибку следует трактовать как исключение?” не существует.Ответственность за решение о том, что считать исключительной ситуацией, возлагаетсяна разработчика. Исключения – это часть интерфейса библиотеки, и решение о том, какиеисключения она возбуждает, – важный аспект ее дизайна. Если библиотекапредназначена для использования в программах, которые не должны аварийно540С++ для начинающихзавершаться ни при каких обстоятельствах, то она обязана разбираться с аномалиямисама либо извещать о них вызывающую программу, передавая ей управление. Решение отом, какие ошибки следует обрабатывать как исключения, – трудная часть работы попроектированию библиотеки.В нашем примере с классом iStack вопрос, должна ли функция push() возбуждатьисключение, если стек полон, является спорным.
Альтернативная и, по мнению многих,лучшая реализация push() – локальное решение проблемы: увеличение размера стекапри его заполнении. В конце концов, единственное ограничение – это объем доступнойпрограмме памяти. Наше решение о возбуждении исключения при попытке поместитьзначение в полный стек, по-видимому, непродуманно. Можно переделать функцию-членvoid iStack::push( int value ){// если стек полон, увеличить размер вектораif ( full() )_stack.resize( 2 * _stack.size() );_stack[ _top++ ] = value;push(), чтобы она в такой ситуации наращивала стек:}Аналогично следует ли функции pop() возбуждать исключение при попытке извлечьзначение из пустого стека? Интересно отметить, что класс stack из стандартнойбиблиотеки C++ (он рассматривался в главе 6) не возбуждает исключения в такойситуации.
Вместо этого постулируется, что поведение программы при попыткевыполнения подобной операции не определено. Разрешить программе продолжать работупри обнаружении некорректного состояния признали возможным. Мы уже упоминали,что в разных библиотеках определены разные исключения. Не существует пригодного длявсех случаев ответа на вопрос, что такое исключение.Не все программы должны беспокоиться по поводу исключений, возбуждаемыхбиблиотечными функциями.
Хотя есть системы, для которых простой недопустим икоторые, следовательно, должны обрабатывать все исключительные ситуации, не ккаждой программе предъявляются такие требования. Обработка исключенийпредназначена в первую очередь для реализации отказоустойчивых систем. В этом случаерешение о том, должна ли программа обрабатывать все исключения, возбуждаемыебиблиотеками, или может закончить выполнение аварийно, – это трудная часть процессапроектирования.Еще один аспект проектирования программ заключается в том, что обработкаисключений обычно структурирована.
Как правило, программа строится из компонентов,и каждый компонент решает сам, какие исключения обрабатывать локально, а какиепередавать на верхние уровни. Что мы понимаем под компонентом? Например, системаанализа текстовых запросов, рассмотренная в главе 6, может быть разбита на трикомпонента, или слоя. Первый слой – это стандартная библиотека C++, котораяобеспечивает базовые операции над строками, отображениями и т.д. Второй слой – этосама система анализа текстовых запросов, где определены такие функции, какstring_caps() и suffix_text(), манипулирующие текстами и использующиестандартную библиотеку как основу. Третий слой – это программа, которая применяетнашу систему.
Каждый компонент строится независимо и должен принимать решения отом, какие исключительные ситуации обрабатывать локально, а какие передавать наболее высокий уровень.541С++ для начинающихНе все функции должны уметь обрабатывать исключения. Обычно try-блоки иассоциированные с ними catch-обработчики применяются в функциях, являющихсяточками входа в компонент. Catch-обработчики проектируются так, чтобы перехватыватьте исключения, которые не должны попасть на верхние уровни программы.
Для этоготакже используются спецификации исключений (см. раздел 11.4).Мы расскажем о других аспектах проектирования программ, использующих исключения,в главе 19, после знакомства с классами и иерархиями классов.542С++ для начинающих5431212. Обобщенные алгоритмыВ нашу реализацию класса Array (см. главу 2) мы включили функции-члены дляподдержки операций min(), max() и sort(). Однако в стандартном классе vectorэти, на первый взгляд фундаментальные, операции отсутствуют.
Для нахожденияминимального или максимального значения элементов вектора следует вызватьодин из обобщенных алгоритмов. Алгоритмами они называются потому, чтореализуют такие распространенные операции, как min(), max(), find() и sort(),а обобщенными (generic) – потому, что применимы к различным контейнернымтипам: векторам, спискам, массивам. Контейнер связывается с применяемым кнему обобщенным алгоритмом посредством пары итераторов (мы говорили о них вразделе 6.5), указывающих, какие элементы следует посетить при обходеконтейнера.
Специальные объекты-функции позволяют переопределить семантикуоператоров в обобщенных алгоритмах. Итак, в этой главе рассматриваютсяобобщенные алгоритмы, объекты-функции и итераторы.12.1. Краткий обзорРеализация обобщенного алгоритма не зависит от типа контейнера, поэтому однаоснованная на шаблонах реализация может работать со всеми контейнерами, а равно и совстроенным типом массива. Рассмотрим алгоритм find(). Если коллекция неотсортирована, то, чтобы найти элемент, требуются лишь следующие общие шаги:1.
По очереди исследовать каждый элемент.2. Если элемент равен искомому значению, то вернуть его позицию в коллекции.3. В противном случае анализировать следующий элемент Повторять шаг 2, показначение не будет найдено либо пока не будет просмотрена вся коллекция.4. Если мы достигли конца коллекции и не нашли искомого, то вернуть некотороезначение, показывающее, что нужного элемента нет.Алгоритм, как мы и утверждали, не зависит ни от типа контейнера, к которомуприменяется, ни от типа искомого значения, однако для его использования необходимы:•способ обхода коллекции: переход к следующему элементу и распознавание того, чтодостигнут конец коллекции.
При работе с встроенным типом массива мы решаем этупроблему, передавая два аргумента: указатель на первый элемент и число элементов,подлежащих обходу (в случае строк символов в стиле C передавать второй аргументнеобязательно, так как конец строки обозначается двоичным нулем);•умение сравнивать каждый элемент контейнера с искомым значением. Обычно этоделается с помощью оператора равенства, ассоциированного со значениями типа, илипутем передачи указателя на функцию, осуществляющую сравнение;•некоторый обобщенный тип для представления позиции элемента внутри контейнераи специального признака на случай, если элемент не найден.
Обычно мы возвращаеминдекс элемента либо указатель на него. В ситуации, когда поиск неудачен,возвращается –1 вместо индекса или 0 вместо указателя.С++ для начинающихОбобщенные алгоритмы решают первую проблему, обход контейнера, с помощьюабстракции итератора – обобщенного указателя, поддерживающего оператор инкрементадля доступа к следующему элементу, оператор разыменования для получения егозначения и операторы равенства и неравенства для определения того, совпадают ли дваитератора. Диапазон, к которому применяется алгоритм, помечается парой итераторов:first адресует первый элемент, а last – тот, который следует за последним.