Министерство образования Республики Беларусь Министерство образования и науки Российской Федерации ГУВПО “Белорусско-Российский университет” Кафедра “Автоматизированные cистемы управления” Дисциплина “Объектно-ориентированное программирование” Лабораторная работа № 9 Обработка исключительных ситуаций Время выполнения работы – 2 часа 2014 1 Цель работы Получение навыков в разработке программ с использованием обработки исключительных ситуаций. 2 Техническое обеспечение 1.1 1.2 1.3 1.4 Персональная ЭВМ с процессором Pentium 2 и более поздних моделей. Клавиатура. Дисплей. Печатающее устройство. 3 Программное обеспечение 3.1 Операционная система Windows 98 и более поздние версии. 3.2 Система программирования Visual C++ версия 6.0 и более поздние версии. 4 Постановка задачи Выполнить задание согласно варианта, приведенного в разделе 7 настоящих методических указаний. 5 Содержание отчета 5.1 Тема и цель работы. 5.2 Текст программы. 5.3 Результаты выполнения программы. 6 Общие сведения Перегруженные операции и конструкторы, в отличие от обычных функций и методов, не могут ни иметь лишние параметры для сигнализации об ошибках, ни возвращать код ошибки. В С++ встроен механизм обработки исключении, с помощью которого методы-операции и конструкторы класса получают возможность сообщать программе-клиенту об аварийной ситуации. Механизм обработки исключений это объектноориентированные средства обработки ошибок, возникающих при неправильной работе программ. 6.1 Механизм обработки исключений В С++ исключение это объект; при возникновении исключительной ситуации программа должна генерировать объект-исключение. Генерация объекта-исключения и создает исключительную ситуацию. Общая схема обработки исключений такова: в одной части программы, где обнаружена аварийная ситуация, исключение порождается; другая часть программы контролирует возникновение исключения, перехватывает и обрабатывает его. Генерируется исключение оператором throw, который имеет следующий синтаксис throw выражение _генерации_ исключения; «выражение_генерации_исключения » обычно означает либо константу, либо переменную некоторого типа; допускается задавать и выражение. Тип объекта-исключения может быть как встроенным, так и определяемым программистом. Для представления вида исключений часто используют пустой класс: class NegativeArgument { }; class ZeroDevide { }; Тогда генерировать исключение можно так: throw ZeroDevide( ); Здесь в качестве выражения генерации исключения использован явный вызов конструктора без аргументов это наиболее часто встречающаяся форма генерации исключения. Объект-исключение может быть и динамическим, например throw new ZeroDevide(); Исключение надо перехватить и обработать. Проверка возникновения исключения делается с помощью оператора try, с которым связаны одна или несколько секций-ловушек catch. Оператор try объявляет контролируемый блок, записываемый в следующем виде try { /* контролируемый блок */ } Контролируемый блок, помимо функции контроля, обладает функциями обычного блока: все переменные, объявленные внутри него, являются локальными в этом блоке и не видны вне его. После try-блока обязательно следует один или несколько catch-блоков, которые называются обработчиками исключений. Форма записи секции-ловушки следующая: catch (спецификация_исключения) { /* блок обработки */} Спецификация исключения может иметь следующие три формы: (тип имя) (тип) (. . .) Тип должен быть одним из допустимых типов исключений либо встроенным, либо определяемым программистом. Первые две формы из приведенных спецификаций исключения предназначены для обработки конкретного типа исключений. Если же на месте спецификации исключения написано многоточие, то такой обработчик перехватывает все исключения. Секцию-ловушку с параметром-многоточием следует указывать в списке catch-блоков последней. Блоки try-catch могут быть вложенными, причем как в try-блок, так и в catch-блок: try { try { } catch(. . .) { } } catch (исключение) { try { } catch(. . .) { } // блок, который может инициировать исключения // вложенный блок // обработка исключения // вложенный блок } 6.2 Передача параметра в блок обработки Первый вариант спецификации исключений означает, что объект-исключение передается в блок обработки, чтобы там его каким-либо образом использовать, например для вывода информации в сообщении об ошибке. При этом объект-исключение может передаваться в секцию-ловушку любым способом: по значению, по ссылке или по указателю, например catch (exception е) catch (exceptions е) catch (const exceptions е) catch (exception *е) // по значению // по ссылке // по константной ссылке // по указателю Предпочтительней выглядит передача параметра по ссылке, так как в этом случае не создается временный объект-исключение. Если тип exception встроенный, никаких преобразований по умолчанию не производится. Это означает, что если в программе написан заголовок обработчика catch (float е) // по значению то попадают в эту секцию только те исключения, тип выражения исключений которых совпадает с float. Оператор throw 1.2 генерирует исключение типа double (константы по умолчанию имеют такой тип), поэтому будет обрабатываться секцией-ловушкой с заголовком, с которой связан тип исключения double. В качестве собственных типов исключений можно реализовать развитый класс, в котором конструкторы имеют аргументы. Это позволит при обработке исключений получать и анализировать нужную информацию. Например, функция вычисляет площадь треугольника по трем сторонам и при этом должна контролировать параметры. Если параметры ошибочны, нужно генерировать исключение. Стороны треугольника могут быть определены и инициализированы как поля класса-исключения ErrorTrianglе. struct ErrorTnangle { double a, b, с; // параметры функции string message; // сообщение // конструктор ErrorTriangle (double x, double y, double z, const string &s) : a(x), b(y), c(z), message(s) { } } Тогда генерация исключения об ошибке параметров может осуществляться так: throw ErrorTriangle (x, у, z, "Сторона нулевой длины"); или throw ErrorTriangle (x, y, z, "Не треугольник"); В секции-ловушке по переданным значениям сторон уточняется причина аварийной ситуации. 6.3 Порядок обработки исключения Когда в try-блоке возникает исключение, начинается сравнение типа исключения и типов параметров в секциях-ловушках. Выполняется тот catch-блок, тип параметра которого совпал с типом исключения. Если такой тип не найден, но есть catch с многоточием, выполняется его блок. Если же такой блок в текущем списке обработчиков не обнаруживается, ищется другой список обработчиков в вызывающей функции. Этот поиск продолжается вплоть до функции main( ). Если же и там не найдется нужного catch-блока, будет вызвана стандартная функция завершения terminate(), в свою очередь, вызывающая функцию abort(). Если catch-блок найден и выполнен, то после выполнения операторов catch-блока при отсутствии явных операторов перехода или оператора throw выполняются операторы, расположенные после всей конструкции try-catch. Если во время работы в try-блоке не обнаружено исключительной ситуации, все catch-блоки пропускаются, и программа продолжает выполнение с первого оператора посте последнего catch. Если управление попало в catch-блок, выход из секции-ловушки выполняется одним из следующих способов. 1. Выполнились все операторы обработчика происходит неявный переход к оператору, расположенному посте конструкции try-catch. 2. В обработчике выполняется оператор goto; разрешается переход на любой оператор вне конструкции try-catch; внутрь контролируемого блока и в другую секциюловушку переход запрещен. 3. В обработчике выполняется оператор return; происходит нормальный выход из функции. 4. В секции ловушке срабатывает оператор throw; такая форма допустима только внутри секции-ловушки; выполнение этого оператора вне ее приведет к немедленному аварийному завершению программы. 5. В обработчике генерируется другое исключение. Оператор throw без выражения генерации исключения генерирует повторное исключение того же типа. Ищется другой обработчик выше по иерархии вложенности. При генерации в секции-ловушке исключения другого типа ищется его обработчик выше по иерархии вложенности. Если нужного catch-блока не обнаруживается, программа аварийно завершается. Выход из обработчика по исключению может привести к выходу из функции. Гарантируется вызов деструкторов для уничтожения локальных объектов. Процесс уничтожения локальных объектов при выходе по исключению называется «раскруткой» (unwinding) стека. Раскрутка стека выполняется только для локальных объектов для динамических объектов, созданных операцией new; деструкторы автоматически не вызываются (они вызываются при выполнении операции delete). 6.4 Исключения в списке инициализации конструктора Инициализировать поля класса можно в списке инициализации конструктора, причем в качестве инициализирующего выражения может выступать вызов функции или явный вызов конструктора. При этом вероятно возникновение исключения. Ловить и обрабатывать такие исключения можно с использованием контролируемого блока уровня функции специального вида (function-try-block). int f(int); class C { int i; double d; public: C(int, double); }; C::C(int ii, double id) try // контролируем : i(f(ii)), d(id) { // . . . } catch(. . .) { // . . . } // функция, определенная в другом месте // поля // конструктор инициализации // реализация конструктора // список инициализации // тело конструктора // обработчик исключений Слово try находится перед списком инициализации конструктора список инициализации контролируется. Любое исключение, которое сгенерирует функция f(). будет перехвачено и обработано н секции-ловушке. При такой организации конструктора отсутствуют операторы после блока try-catch. Выход из данной ситуации только одни осуществить те действия, которые возможно осуществить, и перенаправить исключение дальше, выполнив один из операторов: □ throw без аргумента транслируется перехваченное исключение; □ throw с аргументом генерируется новое исключение. Не все компиляторы поддерживают контролируемый блок уровня функции. Например, и среде Borland С++ Builder 6 эта форма try-блока не реализована. 6.5 Спецификации исключений Для каждой функции, метода, конструктора пли деструктора можно в заголовке указать спецификацию исключений. Если в заголовке функции не указана спецификация исключений, считается, что функция может порождать любое исключение. Однако можно специфицировать в заголовке явный список исключений, которые функция вправе генерировать, например void f1(void) throw( int, double ); void f2(void) throw( NullArgument ); Если в заголовке между скобками спецификации исключений пусто: void f(void) throw(); считается, что функция исключений не генерирует. Наличие спецификации исключений, тем не менее, не является ограничением при реальной генерации исключений функция может возбудить исключение любою типа. Однако если функция генерирует неспецифицированное исключение, запускается стандартная функция unexpected(), которая вызывает функцию terminate(). Это инициирует аварийное завершение программы. Не все компиляторы поддерживают спецификацию исключений в полном объеме в соответствии со стандартом. Например. Visual C++.NET 2003 реализует ее только в следующих формах: □ throw() функция не должна генерировать исключения; □ throw(. . .) функция может генерировать исключения; □ throw(тип) эквивалентна throw(. . .). Если спецификация исключений объявлена в заголовке, то она должна быть указана и во всех прототипах. Несмотря на это, спецификация исключений не входит в прототип функции, поэтому функции с различными спецификациями исключений не считаются разными при перегрузке. 6.6 Подмена стандартных функций завершения При отсутствии подходящей секции-ловушки осуществляется вызов стандартной функции завершения terminate(). Эта же функция вызывается из функции unexpected() при нарушении спецификации исключения. Обе функции можно подменить собственными реализациями. Необходимо подключить заголовок #include <exception> Для подмены стандартной функции terminate() нужно определить собственную функцию с прототипом void F( ); и указать ее имя в вызове функции set_terminate() set_terminate(F); Можно сохранить адрес прежнего обработчика void (*old_terminate) = set_terminate(F); После этого вместо terminate() при обработке неперехваченных исключений будет вызываться наша функция F(). Функция-«терминатор» не должна возвращать управление оператором return, не должна генерировать исключение оператором throw она может лишь завершить программу функцией exit() или abort(). Аналогично реализуется подмена стандартной функции unexpected(): set_unexpected(f); // установка нашей функции Функция обработки неперехваченного исключения, как и терминальная функция, не должна возвращать управление оператором return и завершает программу функцией exit() или abort(). Однако помимо этого она может сгенерировать исключение, указанное в спецификации исключений. Произойдет подмена неперехваченного исключения «легальным», и далее обработка исключений пойдет стандартным образом. Функция может сгенерировать другое исключение, не специфицированное, или просто «отправить» незаявленное исключение «дальше» оператором throw. В этом случае, если в спецификации исключений не указано исключение bad_exception, вызывается функция terminate(). А вот когда спецификация исключений содержит bad_exception, сгенерированное исключение подменяется на bad_exception, и начинается поиск его обработчика. 6.7 Стандартные исключения В языке С++ в составе стандартной библиотеки реализован ряд стандартных исключений, которые организованы в иерархию классов. Листинг 4.1. Иерархия стандартных исключений class exception {// ... }; class logic_error : public exception {// ... }; class domain_error : public logic_error {// ... }; class invalid_argument : public logic_error {// … }; class length_error : public logic_error {// ... }; class out_of_range : public logic_error {// ... }; class runtime_error : public exception {// ... }; class range_error : public runtime_error {// ... }; class overflow_error : public runtime_error {// ... }; class underf1ow_error : public runtime_error {// ... }; class bad_alloc : public exception {// ... }; class bad_cast : public exception {// ... }; class bad_tipeid : public exception {// ... }; class bad_exception : public exception {// ... }; class ios_base::failure : public exception {// ... }; Эта иерархия служит основой для создания собственных исключений и иерархий исключений. Мы вправе определять свои собственные исключения, унаследовав их от класса exception. Класс exception определен в стандартной библиотеке следующим образом: class exception { public: exception () throw(); exception (const exception&) throw(); exception &operator= (const exception&) throw(); virtual ~exception () throw(); virtual const char *what() const throw(); Все конструкторы и методы имеют спецификацию, запрещающую генерацию исключений. Функция-метод what() выдает строку-сообщение об ошибке. Предполагается, что исключения типа logic_error сигнализируют об ошибках в логике программы, например о невыполнении некоторого условия. Категория runtime_error это ошибки, которые возникают в результате непредвиденных обстоятельств при выполнении программы, как, скажем, переполнение при операциях с дробными числами. Такие исключения программа должна возбуждать самостоятельно оператором throw. Пять стандартных исключений порождают различные механизмы С++. Эти исключения тоже можно использовать в операторе throw явным образом, однако делать это не рекомендуется. Исключение bad_alloc генерирует операция new (или new[ ]). если запрос на память не может быть удовлетворен. Исключения bad_cast и bad_typeid возбуждаются механизмом RTTI (Run-Time Type Identification динамическая идентификация типов). Системой ввода/вывода генерируется ios base::failure. Исключение bad_exception описано ранее. Для работы со стандартными исключениями в программе надо прописать оператор #include <stdexcept> Так как обработка исключений сопровождается накладными расходами времени и памяти во время выполнения программы, в интегрированной среде необходимо включить соответствующий режим, разрешающий обработку исключений. 6.8 Создание собственной иерархии исключений Для создания своей иерархии исключений необходимо объявить базовый классисключение, например class BaseCxception { }; Остальные классы объявляются наследниками от него, аналогично тому, как это сделано в иерархии стандартных исключений, например class Child_1exception : public BaseException { }; class Child_2exception : public BaseException { }; Класс BaseException можно унаследовать от стандартного класса exception сlass BaseException : public exception { }; Наследование от стандартных классов позволяет использовать метод what() для вывода сообщения об ошибке. Для наследования от стандартных исключений необходимо включить в программу заголовочный файл #include <exception> Иерархия классов-исключений позволяет вместо нескольких разных блоков-ловушек написать единственный блок с типом аргумента базового класса. В этом случае перехватываются и все исключения-наследники. Такую функциональность обеспечивает принцип подстановки: объект класса-наследника по умолчанию считается объектом базового класса. Это единственное неявное преобразование типов, которое выполняется при передаче параметров в блок обработки. Не запрещается использовать и специализированные исключения. Если требуется несколько секций-ловушек для обработки исключений, то блок с базовым типом исключения должен быть последним как исключение более общего вида. 7 Варианты заданий Во всех заданиях реализуемые функции или методы должны генерировать подходящие исключения. Обработку исключений нужно выполнять главной функцией, которая должна демонстрировать обработку всех перехватываемых исключений. Функции, реализуемые в заданиях, обязаны выполнять проверку передаваемых параметров и генерировать исключение в случае ошибочных. Все функции реализуются в четырех вариантах: □ без спецификации исключений; □ со спецификацией throw(); □ с конкретной спецификацией с подходящим стандартным исключением; □ спецификация с собственным реализованным исключением. Собственное исключение должно быть реализовано в трех вариантах: как пустой класс, как независимый класс с полями-параметрами функции, как наследник от стандартного исключения с полями. Перехват и обработку исключений должна выполнять главная функция. 1. Функция вычисляет площадь треугольника по трем сторонам S p p a p b p c , где р = (а + b + с) / 2. 2. Функция вычисляет корень линейного уравнения ах + b = 0. 3. Функция вычисляет периметр треугольника. 4. Функция переводит часы и минуты в секунды. 5. Функция вычисляет корень квадратного уравнения ах2 + bх + с = 0. 6. Функция вычисляет сумму геометрической профессии Sn = (a0 аnr) / (1 r). 7. Функция выполняет деление комплексных чисел A и В. Комплексные числа представлены структурой-парой (см. задание 14, лаб. работа № 6). 8. Функция вычисляет целую часть неправильной дроби, представленной числителем и знаменателем целыми числами. 9. Функция переводит комплексное число z = х + iу из алгебраической формы в тригонометрическую z = radius(cos(angle) + isin(angle)). Комплексное число представлено структурой-парой (см. задание 1.21). Преобразованное число тоже представляется структурой-парой (radius, angle): y radius x 2 y 2 ; angle arcsin . x 10. Функция вычисляет разность между двумя датами в днях. Даты представлены структурой с тремя полями: год, месяц, день. 11. Функция вычисляет продолжительность телефонного разговора в минутах, принимая время начала и окончания. Время представлено структурой с тремя полями: час, минута, секунда. Неполная минута считается за полную. 12. Функция вычисляет день недели по дате. Даты представлены структурой с тремя полями: год, месяц, день. Первое января считается понедельником. 13. Функция вычисляет углы прямоугольного треугольника. В качестве параметров передаются катеты А и В. (Синус угла А1, противолежащего катету А, вычисляется по формуле sin (Al) = а / с, где с гипотенуза треугольника.) 14. Функция проверяет, является ли передаваемая строка палиндромом. 15. Функция определяет, существуют ли прямые A1x + В1у + С1 = 0 и А2х + + В2у + С2 = 0, если выражение d = А1В2 А2В1 не равно нулю. Прямые задаются структурой с тремя полями. 16. Функция вычисляет расстояние между двумя точками P1(x1, y1) и Р2(x2, y2) по формуле D x1 x2 y1 y2 2 2 . Исключение генерируется, когда P1 и Р2 одна и та же точка. 17. Функция вычисляет расстояние от точки Р(хь у А до прямой Ar + By + С = 0 по формуле Ax1 By1 C D . A2 B 2 18. Функция выясняет, является ли год високосный. Високосность определяется следующим образом: если номер года не делится на 100, то високосным считается тот, который делится на 4 без остатка; если номер года делится на 100, то номер високосного года делится на 400 без остатка. Выполнить задания 118, реализовав подмену стандартной функции unexpected (). Пользовательская функция должна выводить сообщение об отсутствии обработчика исключения и заканчивать работу.