Слайд 1Тема: Обработка исключительных ситуаций в С++
Проблема обработки ошибок
Идея обработки исключений
в С++
Реализация обработки исключений
Раскручивание стека вызовов
Особенности обработки исключений в С++
Классы
исключений
Группировка и иерархия исключений
Слайд 2Сложность задачи обработки ошибочных ситуаций
Концепция программирования с защитой от ошибок
Что считать ошибкой?
«Полицейский» принцип обработки ошибочных ситуаций
Возврат признака завершения
«Регистрация» ошибок
(значение глобальной переменной errno в С)
Обработчики ошибочных ситуаций
Слайд 3Проблема обработки ошибок
Главная проблема обработки исключительных ситуаций состоит в том,
что
разработчику библиотеки классов просто обнаружить возникновение ошибки при выполнении
программы, но сложно принять решение, как с ней поступить.
пользователь знает, что предпринять в случае ошибки, но не имеет возможности ее обнаружить (иначе ошибка была бы обработана в пользовательском коде, и от библиотеки ничего бы не потребовалось)
Слайд 4Идея обработки исключений в С++
Пространственное разделение события возникновения исключения и
его обработки
Понятие исключения, как объекта и обработчика исключения, как функционального
блока, принимающего аргумент
Механизм поиска обработчика путем разматывания стека
Предопределенная обработка исключений
Слайд 5Пример заявления исключения
типа const char*
class String {
public:
// ………………………
char& operator[ ]
( int ){
if( i < 0
|| length() < i )
throw "Index out of range.";
return text[i];
};
// ………………………
};
Слайд 6Использование кода прячем
в try-блок
void foo( String& s ) {
//
. . .
try {
do_something ( s );
}
catch ( const char*
s ) {
// здесь пользователь имеет возможность
// обработать исключительную ситуацию
// например, так
cerr << “Произошла ошибка: ” << s << “\n”;
exit(1);
}
// Здесь продолжается выполнение при нормальном
// завершении функции do_something()
}
Слайд 7Синтаксические элементы
и правила
Слова throw, try и catch являются ключевыми словами
языка С++
Конструкция catch ( . . . . . )
{ . . . . . }
называется обработчиком исключения и должна размещаться сразу за try-блоком, либо за другим обработчиком.
В круглых скобках содержится имя типа и, возможно, имя аргумента
Аргумент может использоваться подобно аргументу функции и передавать обработчику дополнительную информацию, связанную с исключением – значение исключения.
Слайд 8Раскручивание стека вызовов
Процесс генерации и последующей обработки исключений требует поиска
соответствующего обработчика от точки возникновения исключения через последовательность стековых фреймов
вызовов функций до фрейма функции, имеющей искомый обработчик. Говорят, что происходит раскручивание стека вызовов.
Слайд 9Изменение стека вызовов – I
void func2( ) {
int i=256;
}
void
func1( ) {
int k=128;
func2();
}
void main( void ){
int j=64;
func1();
}
младшие
адреса памяти
старшие адреса памяти
стек
Код
Статические данные
Куча
вершина стека
j=64
Слайд 10Изменение стека вызовов – II
void func2( ) {
int i=256;
}
void
func1( ) {
int k=128;
func2();
}
void main( void ){
int j=64;
func1();
}
младшие
адреса памяти
старшие адреса памяти
стек
Код
Статические данные
Куча
вершина стека
j=64
адрес возврата в main()
k=128
фрейм для func1()
Слайд 11Изменение стека вызовов – III
void func2( ) {
int i=256;
}
void func1(
) {
int k=128;
func2();
}
void main( void ){
int j=64;
func1();
}
младшие адреса
памяти
старшие адреса памяти
стек
Код
Статические данные
Куча
вершина стека
j=64
адрес возврата в main()
k=128
фрейм для func1()
адрес возврата в func1()
фрейм для func2()
i=256
Слайд 12Механизмы раскручивания программного стека
Естественное, в процессе выполнения программы;
Связанное с обработкой
исключительных ситуаций;
Путем применения функции longjmp();
Путем прямой модификации регистра указателя стека
CPU
Слайд 13Обрабатываются только исключения, явно генерируемые некоторой функцией;
Поддерживается окончательная модель обработки
(т.е. после возникновения исключения невозможно продолжение выполнения программы от точки
исключения);
Обработка исключения возможна только в функции, вызванной до его появления и еще не завершившейся. После выбрасывания исключения управление должно быть передано некоторому программному блоку, принадлежащему функции, еще находящейся в стеке вызовов, путем его разматывания;
Особенности обработки исключений в языке С++
Слайд 14Если заявлено исключение, для которого нет обработчика в цепочке вызовов,
программа будет завершена. В процессе поиска обработчика программный стек будет
раскручен до конца;
Если обработчик «поймал» исключение, то обработка этого же исключения другими обработчиками, которые могут для него существовать, невозможна. (Другими словами, действует первый подходящий обработчик, встретившийся в процессе разматывания стека);
Слайд 15Если после заявления исключения управление передано catch-блоку, то вне зависимости
от результата последующих действий исключение считается обработанным;
Обработчик, как и
обычная функция, может заявить исключение. Более того, в нем может использоваться оператор throw без параметра, что означает повторное генерирование исключения, обрабатываемого в данный момент;
Слайд 16Обработка исключений и традиционные способы защиты
«Полицейский» подход
можно быть и поумнее
соответствует случаю отсутствия обработчика
Возврат значения-сигнала ошибки
не всегда возможно (например, любой
символ для операции индексации строки допустим)
проверка утомительна, удлиняет и загромождает код
Слайд 17Нормальное завершение с установкой внешнего признака ошибки (errno)
ошибка м.б. незамечена
сколько
признаков надо иметь?
Вызвать функцию-обработчик в случае ошибки (error handler
function)
близок к механизму обработки исключений
нет единого стандарта
проблема программирования функций-обработчиков «по умолчанию»
Слайд 18Что такое исключение?
Исключение выступает одновременно как переменная и как
тип данных (или как объект и как класс)
Оператор throw выбрасывает
объект, а catch-обработчик ловит класс (или throw выбрасывает переменную, а catch ловит ее тип)
Слайд 19Генерирование исключений
При генерации исключения (throw) функции библиотеки исполняющей системы
осуществляют следующие действия
Создается и запоминается копия объекта (переменной). Т.е.
если в точке генерации исключения оказывается недоступен копирующий конструктор (например, он не является public, а исключение заявляет не функция-друг), то возникает сообщение об ошибке.
Разматывается стек, с вызовом деструкторов локальных объектов, выходящих из области видимости
Слайд 20Распознавание исключений
Управление передается ближайшему catch-обработчику, совместимому с типом выброшенного
исключения
При этом копия объекта-исключения передается, если это предусмотрено, обработчику в
качестве параметра.
Слайд 21Обработка исключений
Обработчик считается найденным, а исключение обработанным, если:
тип
исключения соответствует типу, ожидаемому в обработчике. Переменной (объекту) типа T
соответствует обработчик, перехватывающий T, const T, T&, const T&
тип обработчика является публичным базовым классом для заявленного исключения
обработчик ожидает указатель, и исключение является указателем, который может быть преобразован к типу обработчика по стандартным правилам преобразования указателей
встретился обработчик по умолчанию
Слайд 22Обработчик по умолчанию
Обработчик по умолчанию вызывается для исключения любого типа
и имеет вид
catch( ... ) {
// тело обработчика
}
Обработчик по
умолчанию должен располагаться последним среди обработчиков данного try-блока.
Слайд 23Пример
Пусть класс Vector должен обнаруживать ошибки индексации и выделения памяти
class Vector {
public:
class Range{};
class Memory{};
// . .
. . .
Vector(int);
int& operator[ ](int);
// . . . . .
private:
int lbound, ubound;
int *v;
};
Слайд 24Пример (продолжение)
Vector::Vector (int size) {
if( !(v=new int[size]) ) throw Memory();
else
{ lbound = 0; ubound = size – 1; }
}
int&
Vector::operator[](int i) {
if( i < lbound || ubound < i ) throw Range();
else return v[i – lbound];
}
Слайд 25Пример (продолжение)
Пользователь класса Vector может различить два исключения, включив два
обработчика:
void f(void) {
try{
use_vectors();
}
catch( Vector::Range ){
// тело обработчика ошибки индексации
}
catch( Vector::Memory ){
// тело обработчика ошибки выделения памяти
}
}
Слайд 26Повторное заявление исключений
Исключение считается обработанным сразу после входа в обработчик.
Поэтому
try{
do_something();
}
catch( ExeptionType ){
// . . . . .
throw ExeptionType();
}
не
вызовет бесконечного цикла.
Слайд 27Вложенные обработчики
try {
// . . . . .
}
catch( type
) {
try {
//
код, в котором возможно
// возникновение ситуации типа type
}
catch( type ) {
// . . . . .
}
}
не вызовет бесконечного цикла
Слайд 28Тип и значение исключений
При выходе индекса за границы можно передать
ошибочное значение
class Vector {
public:
class Range{
public:
int index;
Range( int i ) : index( i ) { };
};
// . . . . .
int& operator[ ](int);
// . . . . .
private:
int lbound, ubound;
int *v;
};
Слайд 29int& Vector::operator[ ](int i) {
if( i < lbound || ubound
< i ) throw Range(i);
else return v[i-lbound];
}
void f( Vector& v
) {
// . . . . .
try{
use_vectors(v);
}
catch( Vector::Range r ){
cerr << “Bad index: “ << r.index << ‘\n’;
// . . . . .
}
// . . . . .
};
Слайд 30Группировка исключений
enum MathError { Overflow, Underflow, Zerodivide, /*. .
. */ }; // . . . . .
try
{
// . . . . .
MathError m;
throw m;
}
catch( MathError m ) {
switch (m) {
case Overflow : /* . . . . . */ break;
case Underflow : /* . . . . . */ break;
case Zerodivide : /* . . . . . */ break;
default : /* . . . . . */;
}
}
Слайд 31Иерархия исключений
Объектно-ориентированный подход дает лучшее решение
class MathError { };
class Overflow
: public MathError { };
class Underflow : public MathError {
};
class Zerodivide : public MathError { };
// . . . . .
Слайд 32Далее…
try {
// . . . . .
}
catch( Overflow )
{
// обработка Overflow и его потомков
}
catch( MathError ) {
// обработка
любого MathError,
// кроме Overflow и его потомков
}
Слайд 33РЕЗЮМЕ
Задача обработки ошибочных ситуаций сложна и есть разные подходы
С++ предлагает
свой механизм обработки исключений
Для создания кода, обрабатывающего исключения, имеются стандартные
приемы
Используйте классы и объекты для исключений
Используйте иерархию классов для группировки исключений