На этом шаге мы рассмотрим некоторые правила обработки исключений.
Механизм обработки исключений в языке С++ очень похож на соответствующий механизм, существующий в Borland Delphi. Общий вид обработчика исключений следующий:
try {<Операторы контролируемого блока.>} catch (<Спецификация исключения 1.>) {<Операторы 1-го обработчика исключений.>} . . . . catch (<Спецификация исключения N.>) {< Операторы N-го обработчика исключений.>} ,
Как Вы уже, наверное, заметили, механизм исключений в С++ позволяет обрабатывать исключение любого типа. Однако в Visual C++ имеется набор классов исключений, включая CException и несколько производных от него классов: CArchiveException, CDaoException, CDBException, CFileException, CInternetException, CMemoryException, CNotSupportedException, COleException, CResourceException. Более полную информацию об этих классах исключений можно найти в [1, с.502-512]. Абстрактный класс CException имеет конструктор и три функции-члена (метода):
В программе можно генерировать исключения. Это достигается использованием конструкции throw, которая может иметь две формы:
Используя указанные ниже конструкции, можно указывать исключения, которые будет формировать конкретная функция. Например:
Отсутствие в заголовке функции конструкции throw говорит о том, что функция может порождать любую исключительную ситуацию.
Приведем несколько примеров обработки исключительных ситуаций.
#include <iostream.h> class MyException //Класс пользователя для обработки исключений. { protected: char* m_msg; public: MyException(char *msg) { m_msg = msg;} ~MyException(){} char* GetError() {return m_msg;} }; class BigObject { private: int* intarray; public: BigObject() {intarray = new int[1000];} ~BigObject() {delete intarray;} }; int* AllocateBuffer(); int main() { int* buffer; try//Контролируемый блок. { buffer = AllocateBuffer(); delete buffer; } catch (MyException* exception)//Блок обработки исключения. { char* msg = exception->GetError(); cout << msg << endl; } return 0; } int* AllocateBuffer() { BigObject bigarray; float* floatarray = new float[1000]; int* buffer = new int[256]; if (buffer == NULL) { MyException* exception = new MyException("Ошибка при выделении памяти!"); throw exception; } //Генерация исключения. delete floatarray;//Не выполняется в случае генерации исключения. return buffer; }
Программный блок catch не обязательно должен располагаться в той же функции, в которой вызывается исключение. Если блок catch не найден в функции, вызвавшей исключение, то продолжается поиск этого блока в функции, вызвавшей данную и т.п. Если этот блок не будет найден, то выполнение программы прекратится.
В приведенной программе исключение возбуждается в функции AllocateBuffer(), а обрабатывается в функции main(). Когда в функции AllocateBuffer() возникает исключение, остальные операторы в теле функции не выполняются. По этой причине динамически созданный массив floatarray не будет удален. Чтобы этого не произошло функция AllocateBuffer() должна содержать операторы, удаляющие этот массив до вызова исключения:
if (buffer == NULL) { MyException* exception = new MyException("Ошибка при выделении памяти!"); delete floatarray;//Выполнится в случае генерации исключения. throw exception; } //Генерация исключения.
#include <iostream.h> class ZeroDivide {}; //Класс без компонентов. class Overflow {};//Класс без компонентов. float x=1e-20,y=5.5,z=1e+20,w=0.0;//Начальные значения //Определение функции с генерацией исключений. float div (float n, float d) { if (d==0) throw ZeroDivide(); //Вызов конструктора //для создания безымянного объекта. double b=n/d; if (b>1e+30) hrow Overflow(); //Вызов конструктора //для создания безымянного объекта. return b; } //Функция с выявлением и обработкой исключений. void RR() { //Контролируемый блок. try { y=div(4.4,w); z=div(z,x); } //Обработчики исключений. catch (Overflow) {cerr << "\nПереполнение"; z=1e30; } catch (ZeroDivide) {cerr << "\nДеление на нуль"; w=1; } } void main() { RR();//Вызов функции div с нулевым делителем. RR();//Вызов функции div с арифметическим переполнением. cout << "\nРезультат: y= " <<y; cout << "\nРезультат: z= " <<z; }
Деление на нуль Переполнение Результат: y=4.4 Результат: z =1e+30
В программе в качестве типов для исключений используются классы без явно определенных компонентов (ZeroDivide и Overflow). Конструктор ZeroDivide() вызывается и создает безымянный объект при попытке деления на нуль. Конструктор Overflow() используется для создания исключений, когда результат деления превысит величину 1e+30. Исключения указанных типов не передают никакой информации, они используются для обращения к соответствующему обработчику catch.
При первом обращении к функции RR() значение глобальной переменной y не изменяется, так как управление передается обработчику исключений catch (ZeroDivide). При его выполнении выводится сообщение: Деление на нуль и переменная w получает значение 1. После обработчика исключения завершается выполнение функции RR(), и вновь в основной программе вызывается функция RR(), но значение переменной w теперь изменено. Обращение к функции div: div(4.4,w) теперь выполняется безошибочно, но вызов div(z,x) приводит к возникновению исключения типа Overflow. Его обработка в RR() предусматривает вывод сообщения: Переполнение и изменение значения глобальной переменной z. После выхода из RR() основная программа выполняется обычным образом и выводятся значения результатов деления, осуществленного с помощью функции div.
Более подробную информацию по обработке исключений в языке программирования C++, можно получить в разделе "C++/Обработка исключительных ситуаций".
На следующем шаге мы рассмотрим средства отладки в Turbo Prolog 2.0.