Шаг 41.
Средства отладки в Microsoft Visual C++ 5.0. Обработка исключений

    На этом шаге мы рассмотрим некоторые правила обработки исключений.

    Механизм обработки исключений в языке С++ очень похож на соответствующий механизм, существующий в 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 говорит о том, что функция может порождать любую исключительную ситуацию.

    Приведем несколько примеров обработки исключительных ситуаций.


    Пример 1. Перехват исключения вне вызвавшей его функции.
#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; } //Генерация исключения.


    Пример 2. Обработка исключений нескольких типов.
#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++/Обработка исключительных ситуаций".

   


(1) Лейнекер Р. Энциклопедия Visual C++. - СПб.: Питер, 1999. - 1152 с.

    На следующем шаге мы рассмотрим средства отладки в Turbo Prolog 2.0.




Предыдущий шаг Содержание Следующий шаг