Студопедия

КАТЕГОРИИ:

АвтоАвтоматизацияАрхитектураАстрономияАудитБиологияБухгалтерияВоенное делоГенетикаГеографияГеологияГосударствоДомЖурналистика и СМИИзобретательствоИностранные языкиИнформатикаИскусствоИсторияКомпьютерыКулинарияКультураЛексикологияЛитератураЛогикаМаркетингМатематикаМашиностроениеМедицинаМенеджментМеталлы и СваркаМеханикаМузыкаНаселениеОбразованиеОхрана безопасности жизниОхрана ТрудаПедагогикаПолитикаПравоПриборостроениеПрограммированиеПроизводствоПромышленностьПсихологияРадиоРегилияСвязьСоциологияСпортСтандартизацияСтроительствоТехнологииТорговляТуризмФизикаФизиологияФилософияФинансыХимияХозяйствоЦеннообразованиеЧерчениеЭкологияЭконометрикаЭкономикаЭлектроникаЮриспунденкция

Синтаксис инициирования исключительных ситуаций




Следующая функция шлепнет вас по рукам, если вызвать ее с неверным параметром. Вместо линейки она воспользуется оператором throw. В этой функции могут произойти две ошибки, представленные константами перечисления Gotcha.

enum Gotcha { kTooLow, kTooHigh };

void fn(int x) throw(Gotcha)

{

if (x < 0) throw kTooLow; // Функция завершается здесь

if (x > 1000) throw kTooHigh; // Или здесь

// Сделать что-то полезное

}

 

В первой строке определяется тип ИС. Тип ИС может быть любым: целое, перечисление, структура и даже класс. Во второй строке объявляется интерфейс функции с новым придатком – спецификацией исключений, который определяет, какие исключения могут быть получены от функции вызывающей стороной. В данном примере инициируется исключение единственного типа Gotcha. В четвертой и пятой строках показано, как инициируются исключения, которые должны быть экземплярами одного из типов, указанного в спецификации исключений данной функции. Спецификации исключений должны подчиняться следующим правилам.

Объявления и определения.

Спецификация ИС в объявлении функции должна точно совпадать со спецификацией в ее определении.

void Fn() throw(int); // Объявление

// Где-то в файле .cpp

void Fn() throw(int) // Определение

{

// Реализация

}

 

Если определение будет отличаться от объявления, компилятор скрестит руки на груди и откажется компилировать определение.

 

Функции без спецификации исключений.

Если функция не имеет спецификации исключений, она может инициировать любые исключения. Например, следующая функция может инициировать что угодно и когда угодно:

void fn(); // Может инициировать исключения любого типа

Сказанное относится и к функции, объявленной так:

void fn() throw(…); // Может инициировать  ИС любого типа

 

Функции, не инициирующие исключений.

Если список типов в спецификации пуст, функция не может инициировать никакие исключения. Разумеется, при хорошем стиле программирования эту форму следует использовать всюду, где вы хотите заверить вызывающую сторону в отсутствии инициируемых исключений.

void fn() throw(); // Не инициирует ИС

 

Если все же функция fn(), невзирая на обещание, сгенерирует ИС, ей ничего за это не будет, если исполняется отладочная версия программы. Если ее вызов находится в охраняемом участке кода и там есть обработчик соответствующей ИС, то он будет вынужден обрабатывать эту ИС. Если же исполняется окончательная версия программы, то такая ИС не будет перехвачена и обработана имеющимся блоком. По крайней мере так обстоит ситуация для версии Visual C++ 6.0.

 

Функции, инициирующие исключения нескольких типов.

В скобках можно указать произвольное количество типов исключений, разделив их запятыми:

void fn() throw(int, Exception_Struct, char*);

    

Передача исключений.

Если за сигнатурой функции не указан ни один тип исключения, функция не генерирует новые исключения, но может передавать дальше исключения, полученные от вызываемых ею функций.

void fn() throw();

 

Исключения и сигнатуры функций.

Спецификация ИС не считается частью сигнатуры функции. Другими словами, нельзя иметь две функции с совпадающим интерфейсом за исключением спецификации ИС. Две следующие функции не могут сосуществовать в программе:

void f1(int) throw();

void f1(int) throw(MyException); // Повторяющаяся сигнатура!

 

Спецификация исключений для виртуальных функций.

Если виртуальная функция в производном классе объявляется с новой сигнатурой, отсутствующей в базовом классе, эта функция скрывает все одноименные функции базового класса. Аналогичный принцип действует и для спецификаций исключений.

class Foo

{

public:

virtual Fn() throw(int);

};

class Bar : public Foo

{

public:

virtual Fn() throw(char*); // Осторожно!

};

 

Компилятор косо посмотрит на вас, но откомпилирует. В результате тот, кто имеет дело с Foo*, будет ожидать ИС типа int, не зная, что на самом деле он имеет дело с объектом Ваr, инициирующим нечто совершенно иное.

Мораль ясна: не изменяйте спецификацию ИС виртуальной функции в производных классах. Только так вам удастся сохранить контракт между клиентами и базовым классом, согласно которому должны инициироваться только ИС определенного типа.

 

Непредусмотренные ИС.

Если инициированная ИС отсутствует в спецификации исключений внешней функции, программа переформатирует ваш жесткий диск. Шутка. На самом деле она вызывает функцию с именем unexpected(). По умолчанию затем вызывается функция terminate(), но вы можете сделать так, чтобы вызывалась ваша собственная функция.

Соответствующие интерфейсы из заголовочного файла except.h выглядят так:

typedef void (*unexpected_function)();

unexpected_function set_unexpected(unexpected_function excpected_func);

 

В строке typedef... объявляется интерфейс к вашей функции. Функция set_unexpected() получает функцию этого типа в качестве параметра и организует ее вызов вместо функции по умолчанию. Функция set_unexpected() возвращает текущий обработчик непредусмотренных ИС. Это позволяет временно установить свой обработчик таких ИС, а потом восстановить прежний. В следующем фрагменте показано, как используется этот прием.

unexpected_function my_handler(void)

{

// Обработать неожиданную ИС

}

{ // Готовимся сделать нечто страшное и устанавливаем свой обработчик

unexpected_function old_handler = set_unexpected(my_handler);

// Делаем страшное и возвращаем старый обработчик

set_unexpected(old_handler);

}

 

Функция-обработчик не может нормально возвращать управление вызывающей программе, если в ней встречается оператор return или при выходе из области действия функции результаты будут неопределенными. Тем не менее, в функции можно сгенерировать ИС и продолжить поиск перехватчика, подходящего для нового исключения.

 

Особенности работы конструкторов и деструкторов при возникновении ИС.

Одно из принципиальных достоинств стандартной схемы обработки ИС — раскрутка стека (unwinding the stack). При запуске ИС автоматически вызываются деструкторы всех стековых объектов между throw и catch:

void fn() throw(int)

{

Foo aFoo; // объект класса Foo

if (Что-то не так!) throw(bad_news);

}

 

Когда возникает ИС, до передачи стека соответствующему обработчику будет вызван деструктор aFoo. Тот же принцип действует и для try-блока вызывающей стороны:

void main()

{

try

{

  Bar b;

  fn(); // Вызывает ИС

}

catch(int exception)

{

// Перед тем, как мы попадем сюда, будет вызван деструктор b

}

}

 

Вообще говоря, гарантируется вызов деструкторов всех стековых объектов, сконструированных с начала выполнения try-блока. Это может пригодиться для закрытия открытых файлов, предотвращения утечки памяти или для других целей. Тем не менее, дело не обходится без некоторых нюансов.

 

Уничтожаемые объекты.

Гарантируется вызов деструкторов всех стековых объектов, сконструированных с начала выполнения try-блока, но и только. Например, допустим, что к моменту возникновения ИС был сконструирован массив объектов. Деструкторы вызываются лишь для тех объектов массива, которые были сконструированы до возникновения исключения.

Динамические объекты (то есть созданные посредством оператора new) — совсем другое дело. Вам придется самостоятельно следить за ними. Если в куче размещаются объекты, которые должны уничтожаться в результате ИС, то обычно для них создается оболочка в виде вспомогательного стекового объекта.

class TempFoo

{

private:

Foo* f;

public:

TempFoo(Foo* aFoo) : f(aFoo) {}

~TempFoo() { delete f; }

};

try

{

TempFoo tf(new Foo);

// и т.д.

}

catch(...)

{

// Foo уничтожается деструктором tf

}

 

ИС во время конструирования.

Рассмотрим следующий процесс конструирования:

class Foo {...}

class Bar : public Foo

{

private:

A a; // объект класса

B b; // объект класса

public:

Bar();

};

Bar::Bar()

{

X x;

if(плохо) throw(bad_news);

Y y;

}

 

Если во время конструирования объекта воникнет ИС, деструкторы будут вызваны для тех компонентов (базовых классов и переменных), конструкторы которых были выполнены к моменту возникновения ИС. Конструирование Ваr к этому моменту еще не завершено. Тем не менее, конструктор базового класса Foo и объектов а и b уже отработали, поэтому их деструкторы будут вызваны до передачи ИС обработчику. По тем же причинам будет вызван деструктор локального объекта x. Деструктор у не вызывается, поскольку объект еще не сконструирован. Деструктор Bar тоже не вызывается, поскольку конструирование объекта не завершилось к моменту инициирования исключения.

Предположим, конструктор b инициирует исключение. В этом случае вызываются деструкторы Foo и а, но не деструкторы b, Bar и у.

Одни и те же принципы действуют как для стековых, так и для динамических объектов. Если ИС возникает при конструировании динамического объекта, вызываются точно те же деструкторы, что и для стековых объектов.

 

Порядок вызова деструкторов.

Гарантируется, что порядок вызова деструкторов будет обратным порядку вызова конструкторов. Это относится как к локальным переменным, так и к переменным и базовым классам объектов.










Последнее изменение этой страницы: 2018-04-12; просмотров: 363.

stydopedya.ru не претендует на авторское право материалов, которые вылажены, но предоставляет бесплатный доступ к ним. В случае нарушения авторского права или персональных данных напишите сюда...