Тема: Исключительные ситуации. Механизм обработки исключений
ПЛАН ЗАНЯТИЯ №31
Дисциплина: ОП.05 Основы программирования
Преподаватель: Машарова Р.В.
Курс: 3
Группа: 1 ПКС-20
Специальность: Программирование в компьютерных системах
Дата: 19.12.2022
Время проведения: 08.10-09.40, 1 пара
Тема: Исключительные ситуации. Механизм обработки исключений
Цель занятия:
Дидактическая: познакомиться с механизмом обработки исключений
Развивающая: развивать логическое и критическое мышление, умение обобщать и синтезировать знания
Вид занятия лекция
Литература:
1. Павловская Т.А. С/С++. Программирование на языке высокого уровня. 2 изд. – Спб. Питер, 2011. – 464 с.: ил.
2. Павловская Т. А., Щупак Ю. А. C/C++. Структурное и объектно-ориентированное программирование. Практикум. – Спб. Питер, 2010. – 352 с.
3. Семакин И.Г., Шестаков А.П. Основы алгоритмизации и программирования: учебник для студентов учреждений сред.проф. образования – М.: издательский центр «Академия», 2013 – 400 с.
4. Семакин И.Г., Шестаков А.П. Основы алгоритмизации и программирования. Практикум: учебник для студентов учреждений сред.проф. образования – М.: издательский центр «Академия», 2013 – 400 с.
Лекция №60
Тема: Исключительные ситуации. Механизм обработки исключений
1. Механизм обработки исключений
При работе с исключениями используются три ключевых слова try, catch и throw. При этом первые два обозначают так называемый блок try-сatch и связаны с обработкой возникших исключений, а throw – с выбрасыванием исключений. Синтаксически блок try-сatch выглядит следующим образом:
try {
<операторы>}
catch(<описание исключения>) {
<операторы обработки исключения>}
catch(<описание исключения>) {
<операторы обработки исключения>} …
Здесь <операторы> в блоке try представляют собой команды, во время выполнения
которых может произойти исключительная ситуация. В том случае если некоторый набор операторов заключен в блок try, говорят, что операторы находятся в защищенном блоке.
<описание исключения> показывает тип исключения, которое будет обрабатываться в блоке catch. Здесь может быть представлен любой тип данных, как базовый, так и производный. Кроме того здесь может быть описана переменная указанного типа, которую в дальнейшем можно будет использовать при обработке исключения. Помимо описания типа или переменной здесь может стоять троеточие, которое показывает, что данный блок catch обрабатывает исключения любого типа. <Операторы обработки исключения> в блоке catch предназначены для обслуживания возникшей исключительной ситуации определенного типа.
Однако в том случае, если при обработке исключения обнаруживается ошибка, или полностью нейтрализовать возникшую ситуацию не представляется возможным, блок catch сам может быть источником исключений, которые должны отлавливаться и обрабатываться в каком-либо внешнем блоке try-catch.Для генерации исключения внутри блока try должен быть выполнен оператор throw,параметром которого является объект-исключение желаемого типа.
throw<выражение>
Если при выполнении программы достигнут блокtry-catch, то происходит выполнение операторов, находящихся внутри блока try. Если при выполнении этих операторов не возникает исключительной ситуации (не выполняется оператор throw), то выполнение продолжается с оператора, следующего за последним из блоков try, то есть никакие команды из блоков try не выполняются. Если при выполнении операторов в try производится генерация исключения с использованием ключевого слова throw, то на основе объекта, переданного throw, создается объект - исключение и производится поиск соответствующего блока обработки catch. При поиске обработчики просматриваются в порядке следования блоков catch в программном коде. При этом, если соответствующий по типу обработчик не найден, то выполняется поиск во внешнем блоке try-catch (если таковой имеется), по отношению к рассматриваемому. В том случае, если обработчик найти не удается, то производится аварийное завершение программы.
Если соответствующий обработчик найден и его формальный параметр указан, как параметр, передаваемый по значению (catch (<тип><параметр>)), то параметр инициализируется копией объекта-исключения. В том случае, если параметр указывается по ссылке (catch (<тип>&<параметр>)), ссылка инициализируется адресом объекта- исключения.
После инициализации параметра запускается процесс так называемой раскрутки стека. В этом процессе выполняется уничтожение (вызываются деструкторы) всех объектов, созданных внутри блока try и находящихся в автоматической памяти. При этом уничтожение объектов производится в порядке обратном их созданию. После раскрутки стека выполняются <операторы обработки исключения>, располагающиеся в соответствующем блоке catch. В том случае, если при их выполнении никаких исключений не происходит, выполнение продолжается с оператора, следующего за последним из блоков try.
В качестве примера рассмотрим функцию вычисления скорости
doublevelocity( doubledistance, doubletime )
{if( distance< 0 ) throw “ошибка в расстоянии”; if ( time<= 0 ) throw 1;
returndistance / time;}
Здесь функция проверяет значения переданных ей аргументов и выбрасывает исключения разных типов (char* и int), если аргументы некорректны. Фрагмент кода, вызывающего такую функцию может выглядеть следующим образом.
voidtest(){
try {
double d, t, v; cout<<”Введите расстояние:”; cin>>d;
cout<<”Введите время:”;
cin>>t;
v = velocity(d, t); cout<<”скорость: “ << v;}
catch (char* msg) { cout>>msg;}
catch (int) {cout>>”ошибка в параметре ‘время’”;}}
В представленном выше примере мы ожидаем исключения двух базовых типов, при этом для типа int мы не стали вводить какой-либо параметр, так как никакой полезной информации в этом случае мы не ожидаем. На практике чаще всего для работы с исключениями используются классы, при этом класс соответствует типу ошибки, а переменные-члены класса определяют ту полезную информацию, которую можно и целесообразно извлечь из создавшейся ситуации. Например, для приведенного выше примера мы могли бы ввести следующий тип исключений.
classEInvalidArg{ protected:
charm_name[20]; public:
EInvalidArg(constchar *Name) { strncpy(m_name, Name, 20);
m_name[19] = 0;}
voidPrint() { printf( "Параметр <%s> имеет недопустимое " "значение", m_name ); }};
Однако, подхода, при котором классы исключений являются независимыми друг от друга,
следует избегать. Дело в том, что на программиста, использующего такой код, ложится обязанность отлавливать всюду и обрабатывать по отдельности все введенные типы исключений. Чтобы облегчить работу с исключениями и сделать ее более элегантной классы исключений должны образовывать некоторую иерархию. Например:
classEMyException{ protected:
char* m_msg; public:
EMyException( constchar *Msg )
{ m_msg = strdup(Msg); } EMyException( constEMyException&src )
{ m_msg = strdup(src.m_msg); }
~EMyException()
{ free(m_msg); }
const char* Message() { return m_msg; } virtual void Print() { puts( m_msg ); }};
classEInvalidArg: publicEMyException{ protected:
char *m_name; public:
EInvalidArg( constchar *Name ): EMyException("Недопустимый параметр")
{ m_name = strdup(Name); } EInvalidArg( constEInvalidArg&src ):
EMyException(src)
{ m_name = strdup(src.m_name); }
~EInvalidArg ()
{ free(m_name); } virtualvoidPrint()
{printf( "Недопустимый параметр <%s>", m_name ); }};
classEInvalidIndex: publicEMyException{ protected:
intm_min, m_max, m_ind; public:
EInvalidIndex( intMin, intMax, intInd ): EMyException("Неверный индекс")
{m_min=Min; m_max=Max; m_ind=Ind; } EInvalidIndex( constEInvalidIndex&src ):
EMyException(src)
{m_min=src.m_min; m_max=src.m_max; m_ind=src.m_ind;} virtualvoidPrint()
{printf( "Значение индекса (%d) выходит за диапазон”
“ [%d;%d]", m_ind, m_min, m_max );}};
Напомним, что при работе с исключениями создаются копии объектов-исключений.
Поэтому для тех классов исключений, для которых автоматически создаваемый компилятором конструктор копирования некорректен, конструктор копирования должен быть реализован явным образом.С использованием введенных здесь типов исключений операции с объектами класса вектор могут быть реализованы, например, следующим образом:
classCVector{ private:
double *m_pData; int m_iSize;
public:
…
CVectoroperator +( constCVector&v2) {
if (m_iSize != v2.m_iSize) throwEInvalidArg(“v2”); CVectorres(m_iSize);
for( int i = 0; i <m_iSize; i++) res.m_pData[i] = m_pData[i]+v2.m_pData[i];
returnres;
}
double&operator []( intind ) { if (ind< 0 || ind>= m_iSize)
throwEInvalidIndex(0,m_iSize-1,ind); returnm_pData[ind];
}};
Фрагмент кода работы с реализованными выше операциями приведен ниже.
try {
CVector v1(3,3.3), v2(2,2.2), v3;
if (exc_no==1)v1[3] = 1.1; else if (exc_no==2)v3=v1+v2;elseputs(”Исключенийнебудет”);}
catch (constEMyException&e)
{ e.Print(); }catch ( … )
{ puts( "Неизвестное исключение" ); }
Здесь в зависимости от значения целочисленной переменной exc_no будет создана исключительная ситуация, соответствующая одному из введенных выше типов. Обратите внимание, что обработчик исключений здесь принимает параметр не по значению, а по ссылке, что гарантирует появление правильного сообщения при вызове виртуального метода Print (будут выдаваться сообщения «Значение индекса (3) выходит за диапазон [0;2]» и
«Недопустимый параметр <v2>»). Если бы параметр обработчика был указан по значению (catch (EMyException e)), то вызывался бы конструктор копирования для класса EMyException, который создавал бы объект базового класса, и мы бы увидели сообщения без важных деталей («Недопустимый параметр» и «Неверный индекс»).Следует отметить, что, так как при поиске обработчиков они просматриваются в том порядке, в котором записаны, и обработчик считается найденным, если объект-исключение является производным от типа, указанного в обработчике, то типы в обработчике исключений следует располагать в порядке расширения типа. При этом обработчик catch(…), который будет ловить все исключения, следует располагать последним. В ряде случаев обработчик не в состоянии до конца обработать возникшую ошибку. В этом случае он может выбросить новое исключение или повторно сгенерировать то же самое исключение путем вызова throw без параметров, передавая, таким образом, исключение во внешний блок try-catch.
Контрольные вопросы
1. Механизм обработки исключений