Лекция_7_Обработка_исключений.ppt
- Количество слайдов: 32
Обработка исключений Исключительная ситуация, или исключение — это возникновение непредвиденного или аварийного события. Например, это деление на ноль или обращение по несуществующему адресу памяти. Обычно эти события приводят к завершению программы. Но С++ даёт возможность продолжать выполнение программы. Исключения позволяют логически разделить вычислительный процесс на две части — обнаружение аварийной ситуации и ее обработка. 1
Возможные действия при ошибке прервать выполнение программы; n возвратить значение, означающее «ошибка» ; n вывести сообщение об ошибке в поток cerr и вернуть вызывающей программе некоторое приемлемое значение, которое позволит ей продолжать работу; n выбросить исключение. Примечание: cerr – объект класса ostream – связывается с экраном, куда направляет сообщения об ошибках. n 2
Механизм обработки исключений Место с ошибкой должно входить в контролируемый блок: составной оператор, перед которым записано ключевое слово try. n Функция, в которой возникла ошибка, генерирует исключение (используется ключевое слово throw с параметром константой, переменной или объектом) n Отыскивается соответствующий обработчик и ему передается управление n Если обработчик не найден, вызывается стандартная функция terminate, которая вызывает функцию abort (аварийное завершение программы). 3
Синтаксис исключений Контролируемый блок: try{. . . } Генерация исключения (обычно во вложенных в функциях try-блоком): throw [параметр]; Параметр может быть: Переменной, выражением, Ссылкой, конст. ссылкой указателем Обработчики исключений должны располагаться непосредственно за (1 или несколько обработчиков): catch(тип имя){. . . } catch(тип){. . . } catch(. . . ){. . . } – обработка всех необслуженных исключений 4
Перехват исключений Когда с помощью throw генерируется исключение, функции исполнительной библиотеки: 1. создают копию параметра throw в виде статического объекта, который существует до тех пор, пока исключение не будет обработано; 2. в поисках подходящего обработчика раскручивают стек, вызывая деструкторы локальных объектов, выходящих из области действия; 3. передают объект и управление обработчику, имеющему параметр, совместимый по типу с этим объектом. 5
Подходящий обработчик: n n n тот же по типу, что и в параметре catch; является производным от указанного в параметре catch (если наследование производилось с ключом доступа public); является указателем, который может быть преобразован по стандартным правилам преобразования указателей к типу указателя в параметре catch. 6
После обработки исключения управление передаётся первому оператору, находящемуся за всеми обработчиками. n Туда же передаётся управление, если исключение в try-блоке не было сгенерировано. n 7
Пример 1 class A { public: A() { cout << "Constructor of An"; } результат: ~A() { cout << "Destructor of An"; } Constructor of A }; Destructor of A class Error {}; Catch of int class Error. Of. A : public Error {}; void foo() { A a; throw 1; cout << "This message is never printed" << endl; } int main() { try { foo(); throw Error. Of. A(); } catch(int) { cerr << "Catch of intn"; } catch(Error. Of. A) { cerr << "Catch of Error. Of. An"; } catch(Error) { cerr << "Catch of Errorn"; } return 0; 8 }
Пример 2 #include <fstream. h> class Hello{ public: Hello(){cout << "Hello!" << endl; } ~Hello(){cout << "Bye!" << endl; } }; void f 1(){ ifstream ifs("\INVALID\FILE\NAME"); // открытие ф. if (!ifs){ cout << "Генерируем исключение" << endl; throw "Ошибка при открытии файла"; } } void f 2(){ Hello H; // создаём локальный объект f 1(); // вызываем ф-ю, генерирующую исключение } 9
int main(){ try{ cout << "Входим в try-блок" << endl; f 2(); cout << "Выходим из try-блока" << endl; } catch(int i){ cout << “Oбработчик int - " << i << endl; return – 1; } catch(const char * p){ cout << “Oбработчик const char* - " << p; return – 1; } catch(. . . ){cout << “Oбработчик всех" << endl; return – 1; } return 0; } 10
Результаты выполнения программы: Входим в try-блок Hello! Генерируем исключение Bye! Обработчик const char * - Ошибка при открытии файла Таким образом, механизм исключений позволяет корректно уничтожать объекты при возникновении ошибочных ситуаций. Поэтому выделение и освобождение ресурсов целесообразно оформлять в виде классов с конструкторами и деструкторами. 11
Список исключений функции можно указывать в заголовке функции: void f 1() throw (int, const char*){. . . } – функция должна генерировать исключения типов int и const *char; void f 2() throw (Oops*){. . . } - функция должна генерировать исключения типа указателя на класс Oops или производных от него классов; void f() throw (){. . . } – функция не порождает исключений void f() {. . . } – функция может генерировать любое исключение 12
Но указание списка исключений ни к чему не обязывает: n Функция может породить исключение, которое она обещала не использовать. Это приводит к вызову станд. ф-ии unexpected, которая по умолчанию вызывает terminate. n С помощью ф-ии set_unexpected можно установить собственную функцию, которая будет вызываться вместо terminate при неожиданных исключениях. n Функция terminate по умолчанию вызывает ф-ю abort для завершения выполнения программы. С помощью фукнкции set_ terminate можно установить собственную ф -ю, которая будет вызываться вместо abort. 13
Если заголовок функции содержит спецификацию исключений, то каждое объявление этой функции (включая определение) должно иметь спецификацию исключений с точно таким же набором типов исключений. n Виртуальная функция может быть замещена в производном классе функцией с не менее ограничительной спецификацией исключений, чем ее собственная. n 14
Алгоритм обработки исключени я заголовочный фал <exception> 15
Неперехваченные исключения n n n Если исключение сгенерировано, но не перехвачено, вызывается стандартная функция std: : terminate(). Функция terminate() будет также вызвана, если механизм обработки исключения обнаружит, что стек разрушен, или если деструктор, вызванный во время раскрутки стека, пытается завершить свою работу при помощи исключения. По умолчанию terminate() вызывает функцию abort(). 16
Замена стандартного обработчика void Soft. Abort() { cerr << "Program is terminated. " << endl; exit(1); } int main() { set_terminate(Soft. Abort); throw 5; return 0; } 17
Исключения в конструкторах Язык С++ не позволяет возвращать значения из конструкторов и деструкторов. Механизм исключений даёт возможность сообщить о возникшей ошибке. class Vector{ public: class Size{}; // Класс исключения enum {max = 32000}; // Макс. длина вектора Vector(int n) // Конструктор { if (n<0 || n>max) throw Size(); … } …}; try{ Vector *p = new Vector(i); … } catch(Vector: : Size){ … // Обработка ошибки размера вектора } 18
Если в конструкторе объекта генерируется исключение, автоматически вызываются деструкторы для созданных объектов. 19
Исключения в деструкторах Если деструктор, вызванный во время раскрутки стека, выбросит исключение, система вызовет функцию terminate(). Поэтому ни одно из исключений не должно покинуть пределы деструктора. Чтобы выполнить это требование: 1. Не генерируйте исключения в теле деструктора с помощью throw. 2. Рекомендуется инкапсулировать все действия по уничтожению объектов в некотором методе и вызывать его с использованием try/catch: 20
T: : Destroy() { // код, который может генерировать исключения } T: : ~T() { try { Destroy(); } catch(…) { /* … */ } } 21
Стандартные исключения (генерируются операциями или функциями С++) n n n bad_alloc — ошибка при динамическом распределении памяти с помощью new; bad_cast — неправильное использование оператора dynamic_cast bad_typeid — операция typeid не может определить тип операнда; bad_exception — при вызове функции произошло неожидаемое исключение; length_error — попытка создания объекта, большего, чем максимальный размер для данного типа; Библиотечный класс exception описан в <stdexcept> 22
Стандартные исключения n n n domain_error — нарушение внутренних условий перед выполнением действия; out_of_range — попытка вызова функции с параметром, не входящим в допустимые значения; invalid_argument — попытка вызова функции с неверным параметром; range_error — неправильный результат вычислений при выполнении; overflow_error — арифметическое переполнение; underflow_error — исчезновение порядка. 23
Рекомендации n n n выделение и освобождение ресурсов полезно оформлять в виде классов. В этом случае есть гарантия, что при возникновении ошибки информация не будет утеряна. хотя язык С++ позволяет генерировать исключения любого встроенного типа, в реальных системах удобнее создавать специальные классы исключений и использовать в операторе throw либо объекты этих классов, либо анонимные экземпляры. Класс для представления исключения можно описать внутри класса, при работе с которым оно может возникать. Конструктор копирования этого класса должен быть объявлен как public. 24
Рекомендации по программированию с использованием классов Если в процессе анализа постановки задачи выясняется, что необходимости в иерархии классов нет, чаще всего можно обойтись структурным подходом. Смешивать два подхода в одном проекте не рекомендуется. При создании класса, то есть нового типа данных, следует хорошо продумать его интерфейс. Интерфейс хорошо спроектированного класса интуитивно ясен, непротиворечив и обозрим. Как правило, он должен включать только методы, но не поля данных. Поля данных должны быть скрытыми (private). 25
Не следует определять методы типа get/set для всех скрытых полей класса Не нужно расширять интерфейс класса без необходимости. В идеале интерфейс должен быть полным и минимальным. В виде методов рекомендуется определять только действия, реализующие свойства класса. Если какое-либо действие можно реализовать, не обращаясь к скрытым полям класса, лучше описать его как обычную функцию, поместив ее в общее с классом пространство имен. Если функция выполняет действие, не являющееся свойством класса, но нуждается в доступе к его скрытым полям, ее следует объявить как дружественную. Но в общем случае дружественных функций и классов надо избегать. 26
Наиболее часто вызываемые методы можно объявить как встроенные (inline). Конструкторы и деструкторы делать встраиваемыми не рекомендуется. Перегруженные операции класса должны иметь интуитивно понятный общепринятый смысл. Если какая-либо операция перегружена, следует, если возможно, перегрузить и аналогичные операции, например, +, += и ++. При этом операции должны иметь ту же семантику, что и их стандартные аналоги. Для классов, содержащих поля-указатели, следует всегда явно определять конструктор копирования и операцию присваивания. 27
Динамическая память, выделенная в конструкторе объекта, должна освобождаться в его деструкторе. При реализации операции присваивания для классов, содержащих поля-указатели, необходимо проводить проверку на самоприсваивание. Операция присваивания должна возвращать ссылку на константу: class X{ const X & operator=(const X & r); . . . }; const X & X: : operator=(const X & r){ if(this != &r){. . . // Копирование } return *this; } 28
В конструкторах для задания начальных значений полям рекомендуется использовать инициализацию. Поля инициализируются в порядке их объявления. Статические поля не должны инициализироваться в конструкторе. Статические поля инициализируются в глобальной области определения. Конструкторы копирования также должны использовать списки инициализации полей. Операция присваивания не наследуется, поэтому она должна быть определена в производных классах. При этом из нее следует явным образом вызывать соответствующую операцию базового класса. 29
Наследование классов Главное преимущество наследования состоит в том, что можно на уровне базового класса написать универсальный код, с помощью которого можно работать и с объектами производного класса, что реализуется с помощью виртуальных методов. Методы, которые должны иметь все производные классы, но которые не могут быть реализованы на уровне базового класса, должны быть виртуальными. Деструкторы объявляются как виртуальные. При переопределении виртуальных методов нельзя изменять наследуемое значение аргумента по умолчанию. Невиртуальные методы переопределять в производных классах не рекомендуется. 30
Открытое наследование класса Y из класса X означает, что Y представляет собой разновидность класса X. Альтернативным наследованию методом использования одним классом другого является вложение, когда один класс является полем другого. Вложение представляет отношения классов “Y содержит X” или “Y реализуется посредством Х”. Следует предпочитать вложение наследованию. Когда между классами нет логической взаимосвязи, а требуется использовать часть кода одного класса в другом, может быть полезным использовать закрытое наследование. Этот способ используется, когда в производном классе требуется доступ к защищенным элементам базового класса и замещение его виртуальных методов. 31
Шаблоны классов используются для создания семейств классов, поведение которых не зависит от типа объектов. Шаблоны следует использовать аккуратно, отдавая себе отчет в том, что для каждого типа порождается собственная копия шаблона, что может привести к разбуханию кода. Исключения используются, как правило, в тех случаях, когда иного способа сообщить об ошибке не существует, а также когда ошибка неисправимая или очень редкая и неожиданная. Обработка исключений несколько уменьшает производительность программы. 32
Лекция_7_Обработка_исключений.ppt