Компания ALT Linux
Опубликован: 07.03.2015 | Доступ: свободный | Студентов: 2184 / 522 | Длительность: 24:14:00
Лекция 10:

Объектно-ориентированное программирование

10.5.3 Иерархия исключений

Классы-индикаторы исключения могут принадлежать к общей иерархии наследования, т. е. быть в отношениях "родитель-потомок". При этом обработчики индикаторов-родительских классов могут перехватывать исключения с индикаторами-потомками (можно считать такое поведение проявлением полиморфизма). Поэтому родительские обработчики нужно обязательно указывать после дочерних в цепочке блоков catch — иначе дочерний обработчик никогда не получит управление. В самом конце цепочки можно указать catch, у которого в круглых скобках вместо индикатора три точки. Такой блок будет перехватывать абсолютно любые исключения:

class general_error { };
class out_of_range : public general_error { };
............. .
try {...............}
catch ( out_of_range )
{ cout << "Выход индекса за границу массива\n "; }
catch ( general_error )
{ cout << "Общий сбой в работе программы\n "; }
catch (...) { cout << "Неизвестная ошибка\n "; }

В приведённом схематичном примере мы объявили два различных класса- индикатора, один базовый, для исключений общего типа, и один производный от него, для исключительной ситуации типа "недопустимый индекс при обращении к в массиву". Если бы порядок следования обработчиков был другим, обработчик индикатора out_of_range никогда не смог бы активироваться.

Если обработчик перехватил исключение, но обнаружил, что не сможет справиться с его обработкой, он может вызвать throw без аргументов: это передаст исключение дальше по цепочке уровней вложенности, на случай если на более высоком уровне есть обработчик, способный так или иначе решить возникшую ситуацию. Проиллюстрируем повторное возбуждение исключения, изменив пример из п. 10.5.2. Цикл обработки событий мы поместим в отдельную функцию main_loop(), принимающую в качестве аргумента ссылку на массив. Соответственно, создание объекта массива и передачу его в цикл обработки событий поместим в ещё один блок try, с обработчиком, принимающим исключение типа general_error. В первую очередь это позволит корректно обрабатывать ошибку нулевой ёмкости массива. Для иллюстрации передачи повторно сгенерированного исключения из внутреннего обработчика внешнему специально добавим инструкцию throw без аргументов в обработчик события out_of_range (таким образом, выход индекса за границу массива станет фатальной ошибкой, приводящей к остановке программы). Чтобы исключение могло быть успешно перехвачено на внешнем уровне вложенности, сделаем класс general_error родительским для остальных классов-индикаторов исключений.

#include <iostream>
using namespace std;
class general_error
{
public :
	char *message;
	general_error ( char* message ) { this->message=message; }
};
class out_of_range : public general_error
{
public :
	size_t i;
	out_of_range ( size_t i );
};
out_of_range::out_of_range ( size_t i )
: general_error ( "Выход индекса за границу массива" )
{ this->i= i; }
	class overflow : public general_error
{
public :
	overflow ( );
};
overflow::overflow ( ) : general_error ( "Переполнение массива" ) {}
class underflow : public general_error
{
public :
	underflow ( );
};
underflow::underflow ( ) : general_error ( "Массив пуст" ) {}
class array
{
	size_t size; //реальный размер массива
	size_t capacity; //максимально-допустимый размер
	double * data;
public :
	array ( size_t capacity ) throw ( general_error );
	˜ array ( );
	double operator [ ] ( size_t i ) throw ( out_of_range ); //получить значение i-го
	элемента
	void push ( double v ) throw ( overflow ); //добавить элемент
	double pop ( ) throw ( underflow ); //убрать последний добавленный элемент
};
array::array ( size_t capacity ) throw ( general_error )
{
	if ( capacity==0) throw
general_error ( "массив нулевой вместимости" );
	this->capacity=capacity;
	size =0;
	data.new double [ capacity ];
}
array::˜ array ( ) {
	delete [ ] data;
}
double array::operator [ ] ( size_t i ) throw ( out_of_range )
{
	if ( i < size ) return data[ i ];
	else throw out_of_range ( i );
}
void array::push ( double v ) throw ( overflow )
{
	if ( size < capacity ) data[size++]=v;
	else throw overflow ( );
}
double array::pop ( ) throw ( underflow )
{
	if ( size > 0 ) return data [-- size ];
	else throw underflow ( );
}
void main_loop ( array& a )
{
	char c;
	double v;
	size_t i;
	for (;; )
	{
		cout << "Введите \ " + \ " для добавления элемента, "
		" \ " - \ " для извлечения, \" i \" для просмотра "
		" i-го элемента, \" a \" для выхода: ";
		cin >> c;
		try {
			switch ( c ) {
			case " + " :
				cout << "Введите добавляемое число: ";
				cin >> v;
				a.push ( v );
				break;
			case " - " :
				v=a.pop ( );
				cout << "Извлечено число " << v << endl;
				break;
			case " i " :
				cout << "Введите индекс: ";
				cin >> i;
				v=a[ i ];
				cout << "Искомый элемент равен " << v << endl;
				break;
			case " a::
				return;
				break;
			}
		}
		catch ( out_of_range& e )
		{
			out << "Попытка доступа к элементу с недопустимым индексом " << e.i << endl;
			throw;
		}
		catch ( overflow )
		{
			cout<<"Операция не выполнена, так как массив переполнен\n ";
		}
		catch ( underflow ) {
			cout << "Операция не выполнена, так как массив пуст\n ";
		}
	}
}
main ( )
{
	double v;
	try
	{
		cout << "Введите ёмкость массива: ";
		cin >> v;
		array a( v );
		main_loop ( a );
	}
	catch ( general_error& e )
	{
		cout << "Произошла неустранимая ошибка следующего типа: " << e.message << endl;
	}
}

10.5.4 Спецификация исключений

Если некоторая функция содержит инструкции throw, в её заголовке можно явно прописать, какие исключения она может генерировать:

void f() throw (x,y,z);

В примере функция f() может генерировать исключения с классами- индикаторами x, y, z (и производными от них). Если заголовок описан как "void f() throw ()" — функция не генерирует исключений вообще. И, наконец, если в заголовке функции ничего не указано, она может генерировать любые исключения (без этого последнего правила не смогли бы работать программы, не использующие обработку исключений).

Если функция попытается сгенерировать исключение, отсутствующее в её списке — вызовется специальная функция void unexpected(), которая в свою очередь вызовет фунцкию void terminate(), а та вызовет функцию abort(), аварийно завершающую программу. Программист имеет возможность заменить функцию unexpected() или функцию terminate() — или сразу обе — на свои собственные, изменив таким образом обработку неспецифицированных исключений. Для такой замены нужно вызвать специальные библиотечные функции set_unexpected() и set_terminate(), передав им адреса новых функций в качестве аргументов. Наглядно можно увидеть действие этих функций в примере из раздела 10.4.2, в котором не перехватывается исключение general_error. Это исключение выбрасывается в примере конструктором класса array в том случае, если пользователь попытается создать массив нулевой ёмкости. Оно оказывается необработанным, т. к. создание массива находится за пределом блока try. К сожалению, стандартная функция не может знать о структуре класса-индикатора, и потому текстовое сообщение, оставленное конструктором, оказывается невостребованным. Программа сообщает имя неперехваченного класса-индикатора, после чего выполняет аварийное завершение работы.

Сергей Радыгин
Сергей Радыгин

Символы кириллицы выводит некорректно. Как сделать чтобы выводился читабельный текст на русском языке?

Тип приложения - не Qt,

Qt Creator 4.5.0 основан на Qt 5.10.0. Win7.

 

Юрий Герко
Юрий Герко

Кому удалось собрать пример из раздела 13.2 Компоновка (Layouts)? Если создавать проект по изложенному алгоритму, автоматически не создается  файл mainwindow.cpp. Если создавать этот файл вручную и добавлять в проект, сборка не получается - компилятор сообщает об отсутствии класса MainWindow. Как правильно выполнить пример?