Необработанные исключительные ситуации 


Мы поможем в написании ваших работ!



ЗНАЕТЕ ЛИ ВЫ?

Необработанные исключительные ситуации



В предыдущем разделе демонстрировалось, как обработать все возможные исключительные ситуации, од­нако вы можете задуматься: "А что произойдет, если возникшая исключительная ситуация нигде не обработа­ется?" Ответ прост и логичен. Необработанные исключительные ситуации передаются вверх по цепочке вызо­вов (имеется в виду цепочка вызовов одних функций другими) до тех пор, пока не встретится соответствую­щий им оператор catch или пока больше не останется нерассмотренных обработчиков исключительных ситуа­ций. Если произойдет последнее, для обработки исключительной ситуации вызывается одна из трех специаль­ных функций, автоматически присоединяемых к каждой программе на C++, использующей исключительные ситуации. Эти функции имеют имена unexpectedO, terminateO и abortO. Они вызываются в соответствии со следующими правилами.

• Если в программе возникли исключительные ситуации, которые не обрабатываются оператором catch, то вызывается функция unexpectedO. Исключительные ситуации, которые не обрабатываются ни одним оператором catch, называются непредвиденными исключительными ситуациями. По умолчанию функция nexpectedO вызывает функцию terminateO, назначение которой поясняется ниже.

Непредвиденные исключительные ситуации могут возбуждаться программой при обнаружении повреждения стека или деструктором класса (опасная практика, резервируемая только для наиболее критических проблем), вследствие чего вызывается функция terminateO. По умолчанию функция terminateO вызывает функцию abortO, назначение которой объясняется ниже.

• Функция abort О находится ниже всех на тотемном столбе и немедленно прерывает выполнение программы. Она никогда не вызывается непосредственно. Если не обработаны все возможные исключительные ситуации и не приняты дополнительные меры для перепрограммирования функций nexpectedO и, возможно, terminateO, необработанная исключительная ситуация аварийно прервет выполнение программы путем обращения к abortO. Если программа добралась до этой стадии, ничто уже не поможет вам предотвратить ее завершение. (Между нами говоря, если программа непредвиденно
завершается, вам следует заняться обработкой ошибок!)

ЗАМЕЧАНИЕ

В исходном коде исполняющей системы Borland C++ функция terminateO -не вызывает непосредственно функцию abortO, как это диктует ANSI C++. В Borland C++4.5 функция terminateO просто завершает программу. Поскольку вы не можете заменить функцию abortO, это небольшое несоответствие не имеет отрицательных последствий, но вообще полезно думать, что в стандартной ситуации функция terminateO вызывает abortO.

Для демонстрации того, что происходит, когда программа не обрабатывает все возможные исключительные ситуации, сохраните копию ЕХСЕРТ2.СРР под другим именем (я использовал Х.СРР). Перепишите в ней функцию main О следующим образом:

int main()

Instruct();

Run 0;

return 0;

Любые необработанные исключительные ситуации из функции Run() заставляют программу вызывать функцию unexpectedO, вызывающую функцию terminateO, которая, в свою очередь, вызывает функцию abortO, завершающую выполнение программы. Приложения для DOS, завершающиеся подобным образом, выводят сообщение "Abnormal program termination" (Ненормальное завершение программы). WINDOWS- и EasyWin-приложения завершаются, отображая окно диалога, показанное на рис. 15.2. За исключением про­стейших примеров и тестовых программ, вы обязаны сделать все возможное для предотвращения такого не­предвиденного завершения.

Вы можете заменить функции unexpectedO и terminateO своим кодом для обработки необработанных ис­ключительных ситуаций. Например, имеет смысл заменить функцию unexpectedO для уведомления пользова­теля о любых необработанных исключительных ситуациях на этапе разработки программы. Это может помочь обнаружить недостающие обработчики ошибок в программе. В других случаях можно заменить terminateO ди­агностирующим кодом для просмотра памяти в надежде обнаружить операторы, повреждающие кучу, или для поиска причин возбуждения деструктором класса исключительной ситуации.

Заменить функцию abortO вы не можете. Вызов функции abortO всегда приводит к завершению програм­мы без всяких "если", "но" или "потом как-нибудь".

Замена функций unexpectedO и terminateO

Для задания адреса вашей собственной функции-обработчика непредвиденных исключительных ситуаций следует использовать функцию set_unexpected(), объявленную в заголовочном файле EXCEPT. H. Функция: должна иметь тип unexpected_function, не иметь аргументов и ничего не возвращать.

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

Для задания адреса вашей собственной функции-обработчика завершения программы следует использовать функцию set_terminate(), также объявленную в заголовочном файле EXCEPT.Н. Функция должна иметь тип terminate_function, не иметь параметров и ничего не возвращать.

Обе функции — set_unexpected() и set_terminate() — возвращают адрес текущей функции-обработчика. Вы можете сохранить и затем восстановить существующие обработчики, запомнив их адреса в переменных, а затем передав их обратно функциям. Например, сначала объявляется прототип функции-обработчика:

void unexpectedHandlerO;

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

set_unexpected(unexpectedHandler);

Или же вы можете сохранить, а затем восстановить адрес прежней функции-обработчика, хотя это делать необязательно:

unexpected_function savedAdress; // Объявить адресную переменную

savedAdress = set_unexpected(unexpectedHandler); // Установить обработчик

//... Новый обработчик перехватывает непредвиденные исключительные ситуации set_unexpected(savedAdress); // Восстановить первоначальный обработчик //... Новый обработчик больше не используется

Пользовательская функция terminateO устанавливается аналогично, только вместо set_unexpected() вызы-! вается функция set_terminate(), возвращающая адрес типа terminate_function.

В листинге 15.18 демонстрируется установка обработчиков непредвиденных исключительных ситуаций и за­вершения. В программе также демонстрируется приемлемый способ обработки непредвиденных исключительных ситуаций неизвестных типов, которые могут возбуждаться в плохо документированных библиотечных функциях. Вы можете скомпилировать и запустить следующую программу как DOS- или EasyWin-приложение.

Листинг 15.18. UNEXPECT.CPP (установка функций-обработчиков unexpectedO и terminateO)

#include <iostream.h> Sinclude <except.h>

«define MAXERR 10

// Класс, используемый, когда число ошибок превышает допустимое class MaxError

// Обычный класс исключительных ситуаций

class Error {

public:

Error(); // Конструктор

void Say(); // Функция, сообщающая о числе ошибок private:

static int count; // Доступен только в этом классе }

// Прототипы функций, возбуждающих исключительные ситуации void Run() throw(Error); void trapper();

void zapper();

// Глобальный статический счетчик числа объектов класса Error int Error:: count;

void main() {

set_unexpected(t rappe r);

set_terminate(zapper);

for (;;)

try {

Run(); > catch (Error e) {

e.Say(); >

// Функция, возбуждающая исключительную ситуацию

void Run() throw(Error)

{

// throw Error();

throw "An unknown exception object"; }

// Пользовательский обработчик непредвиденных ошибок

void trapper()

<

cout «endl «"Inside trapper function" «endl;

throw Error();

// Пользовательский обработчик завершения ошибки

void zapper()

{

cout «endl «"Inside terminate function" «endl;

cout «endl «"Exiting program with error code -1" «endl; (1)

// Конструктор класса Error Error::Error()

count++;

if (count > MAXERR) throw MaxError();

// Функция класса Error, выводящая сообщения void Error::Say() { cout << "Error: count = " «count «endl;

При запуске UNEXPECT выведутся десять сообщений об ошибках перед завершением. На рис. 15.3 демон­стрируется результат работы программы.

В программе используются два класса исключительных ситуаций. Объект класса МахЕггог, не имеющего данных или функций, посылается, когда число ошибок превышает константу MAXERR, заданную в этом при­мере равной 10. Объекты класса Error посылаются так же, как и в предыдущих примерах.

Конструктор класса Error, располагающийся ближе к концу листинга, инкрементирует статический член клас­са count. Поскольку член count — статический, существует только один экземпляр этого значения, и он сущест­вует до тех пор, пока программа не завершится. Закрыв член count в классе, мы обезопасили это важное значение от изменения в операторах программы вне класса. Только функции-члены класса Error имеют непосред­ственный доступ к счетчику ошибок программы. Функция-член Say О отображает значение члена count.

На пути к созданию объекта класса Error, если член count больше или равен константе MAXERR, конст­руктор возбуждает исключительную ситуацию типа МахЕггог. При возбуждении исключительной ситуации объект класса не создается, поэтому создание объекта класса Error прерывается (более подробно об этом вы узнаете позже).

В функции mainO устанавливаются функции-обработчики, замещающие по умолчанию функции unexpectedO и terminateO. В основной программе затем выполняется "бесконечный" цикл for, похожий на те, что вы уже видели раньше. Внутри цикла в блоке try вызывается функция Run(), и оператор catch перехва­тывает все объекты класса Error, которые посылает Run(). При возбуждении исключительной ситуации catch отображает текущее значение счетчика ошибок путем обращения к функции-члену SayO посланного объекта.

Функция Run() всегда возбуждает исключительную ситуацию, моделируя возникновение нескольких оши­бок, которые может понадобиться обработать в более обширных программах. Пользовательский обработчик непредвиденных исключительных ситуаций trapperO выводит сообщение, свидетельствующее о вызове этой функции. Как показано на рис. 15.3, это происходит после возбуждения 10 исключительных ситуаций.

Обработчик непредвиденных исключительных ситуаций может возбудить еще одну исключительную си­туацию для продолжения программы. В данном примере следующий оператор посылает новый экземпляр класса Error:

throw ErrorO;

Поскольку наш обработчик непредвиденных исключительных ситуаций не вызывается до тех пор, пока не возникнут 10 исключительных ситуаций, тем не менее создание объекта класса Error заставляет конструктор этого класса возбудить еще одну исключительную ситуацию типа MaxError (см. конструктор класса Error). Этот тип ошибки не поддерживается в операторе catch, поэтому вызывается обработчик завершения програм­мы zapper. Функция завершения не может возбудить исключительную ситуацию. (Функции завершения и не­предвиденных исключительных ситуаций также не могут выполнить оператор return).

На этой стадии кажется, что нет особого смысла задавать два обработчика, которые вызываются перед за­вершением программы. Что действительно необходимо, так это метод перехвата непредвиденных исключитель­ных ситуаций неизвестных типов, позволяющий при этом программе выполняться дальше. Вам все еще необ­ходим обработчик завершения, вызывающийся только в случае превышения программой лимита ошибок.

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

void Run() throw(Error) {

throw "An unknown exception object"; >

Когда вы скомпилируете и запустите программу, обработчик непредвиденных ситуаций trapperO будет вы­зываться для каждой исключительной ситуации, возбуждаемой функцией Run(). Это происходит потому, что не существует оператора catch для объектов исключительных ситуаций типа const char* или char*. Функция trapperO, тем не менее, транслирует нераспознанные объекты исключительных ситуаций, посылая объект известного типа, в данном случае, Error. Новая возбужденная исключительная ситуация продолжает выполне­ние программы в операторе catch внутри функции mainO, который обрабатывает транслированную ис­ключительную ситуацию.

В конце концов, в программе максимальное число ошибок будет превышено, и объект класса МахЕггог бу­дет послан конструктором Error. В результате вызовется обработчик завершения программы путем обращения к библиотечной функции exit().

Исключительные ситуации и локальные объекты

С помощью специальной опции компилятора локальные объекты, оставшиеся в стеке, автоматически уничтожаются возбуждением исключительной ситуации, т.е. вызываются деструкторы этих объектов. Для раз­решения автоматического уничтожения задайте опцию -xd для автономного компилятора. Или же из интегри­рованной среды выберите Options\Project..., откройте пункт C++ Options и выберите подпункт Exception handling\RTTI. (RTTI — Runtime Type Information, информация о типах времени исполнения). Убедитесь, что установлены пункты Enable exceptions и Enable destructor cleanup.

ЗАМЕЧАНИЕ

Вы обязательно должны устанавливать автоматическую очистку деструкторами для ObjectWindows-nporpaMM
(OWL).

В целом автоматическое уничтожение необходимо для функций, которые

• создают локальные объекты,

• возбуждают исключительные ситуации после создания объектов.

Простой пример демонстрирует, почему эти два пункта важны для вас. Следующая функция может оста­вить объект класса AnyClass в стеке:

int AnyFunction() {

AnyClass object(123);

//....

if (condition) throw Error(); return object.Value(); }

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

Использование ключа -xd автономного компилятора или аналогичных установок в интегрированной среде гарантирует, что в подобных случаях деструктор для object будет вызван по завершению функции в результа­те возбуждения исключительной ситуации. Обычно такая установка желательна, однако ее использование приводит к накладным расходам, их можно избежать, если в вашей программе нет локальных объектов в функциях, в которых возбуждаются исключительные ситуации. (Не забудьте, что вы обязаны в любом случае установить опцию автоматического уничтожения для программ, использующих OWL.)

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

 

Избежать дублирования операторов delete можно только тогда, когда исключительная ситуация возбужда­ется после уничтожения динамического объекта:

int AnyFunction() {

AnyClass *p = new AnyClass(123);

//.-

delete p;

if (condition) throw ErrorQ; return 123; }

Используйте флаг, подобный condition, чтобы указать наличие ошибок, удалить все динамически выделен­ные объекты, а затем возбудить исключительную ситуацию.

Исключительные ситуации и конструкторы

Конструкторы класса могут возбуждать исключительные ситуации для сигнализации о том, что они не мо­гут создать объект. До сих пор в C++ не было другой возможности сообщить о неудавшемся создании объекта класса, кроме обращения к функции-члену, возвращающей истину или ложь в зависимости от того, удалось создать объект или нет.

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

class AnyClass { public:

AnyClass()

{ if (condition) throw ErrorQ; }

"AnyClass() { } };

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

class AnyClass { OtherClass x; public:

AnyClassO: x(123)

{ if (condition) throw Error(); } "AnyClassO {} };

Объект x типа OtherClass создается конструктором AnyClass до того, как будут выполняться операторы в теле конструктора. Если конструктор OtherClass возбуждает исключительную ситуацию, прочие операторы конструктора не выполняются. Поскольку код конструктора не выполнился, объект класса AnyClass не созда­ется, и, следовательно, деструктор AnyClass не вызывается. Тот же механизм действует и в случае классов, обладающих несколькими объектами:

class AnyClass {

OtherClass a, b, с; public:

AnyClass: a(), b(), c() { }

"AnyClassO { }

Если конструктор объекта b возбуждйет исключительную ситуацию, объект с не создается, как и объект класса AnyClass. Деструктор объекта а вызывается как обычно (поскольку он был полностью создан до того, как b возбудил исключительную ситуацию), но деструкторы для объектов Ь, си AnyClass не вызываются.

Возбуждение исключительных ситуаций

Класс может также возбудить исключительную ситуацию своего же типа. Обычно это делается с помощью функции-члена, которую часто называют RaiseO. Например, вы можете создать класс исключительной ситуа­ции следующим образом:

class Error { public:

void RaiseO

{ throw Error(); } };

Если е — объект класса Error, оператор

e.RaiseO; // Возбудить исключительную ситуацию класса Error

возбуждает исключительную ситуацию, посылая еще один объект этого же класса. В зависимости от сервиса, предоставляемого классом Error, создание нового объекта исключительной ситуации может лучше удовлетворить ваши запросы, чем повторное возбуждение следующей исключительной ситуации с уже существующим объектом:

throw e; // Возбуждение исключительной ситуации с существующим объектом

Исключительные ситуации и управление распределением памяти

Как упоминалось ранее, оператор new возбуждает исключительную ситуацию, если не может удовлетво­рить запрос выделения памяти, в отличие от более ранних версий C++, в которых возвращался null (нуль) в случае нехватки памяти.

ЗАМЕЧАНИЕ


Если вы хотите, чтобы new действовал по-старому, возвращая нуль в случае возникновения ошибок, включите в программу заголовочный файл NEW.H и добавьте в функцию mainO следующий оператор:

bet_r.ew_nardler(O);

Раньше вы, вероятно, написали бы следующий код:

AnyClass *p; // Объявить указатель на AnyClass

р = new AnyClassO; // Создать объект типа AnyClass, адресуемый р

if (р == 0) Error(); // Вызвать процедуру, если не хватает памяти

Если после использования оператора new для выделения памяти объекту класса AnyClass указатель р содер­жит нуль, это значит, что new не удалось выделить достаточно памяти для создания объекта. Сложность в том, что необходимо вьтолнять проверку возвращаемого значения new после каждого использования оператора.

Исключительные ситуации предлагают иной способ использования new. Теперь при нехватке памяти опе­ратор new возбуждает исключительную ситуацию типа xalloc, определенного в заголовочном файле EXCEPT. H следующим образом:

class xalloc: public xmsg { public:

xalloc(const string &msg, size_t size);

size_t requested() const;

void raiseO throw(xalloc); private:

size_t siz;

Для использования исключительных ситуаций вместе с new заключите операторы, выделяющие память, в блок try, за которым должен следовать оператор catch для объекта класса xalloc. Например, вы можете напи­сать следующее:

try {

р = new AnyClassO;

//... прочие операторы, выделяющие память

}

catch (xalloc x) {

cout «"Memory error:)

exit(-2);

}

Класс xalloc выводится из другого класса — xmsg, также объявленного в заголовочном файле EXCEPT. H. Класс-предок содержит текстовое сообщение типа string (которое описывается далее в этой главе). Для досту­па к этой строке, как показано в предыдущем фрагменте кода в операторе catch, следует использовать функ­цию-член why О. Для определения размера неудавшегося запроса выделения памяти в байтах следует исполь­зовать функцию x.requestedO.

Вы также можете использовать один блок try в функции mainO для обработки всех возникающих ошибок. Например, вашу функцию mainO можно структурировать следующим образом:

void main()

 

for (;;){

try{

Run();// Запустить программу

}

catch (xalloc x) {

cout «"Out of memory" «endl;

exit(-1); // Завершить программу или выполнить другие действия }

}}}

В этом примере идея заключается в вызове единственной функции Run(), запускающей программу. Любые исключительные ситуации будут обрабатываться в функции Run() (или в другой функции, которую вызывает.! RunO) либо в функции main(), где вы можете обработать их с помощью оператора catch.

В листинге 15.19 модифицированной версии программы MEMERR из главы 12, демонстрируется исподы зование исключительных ситуаций вместе с оператором new для "изящной" обработки ошибок нехватки памя­ти. Вы можете скомпилировать и запустить эту программу как DOS- или EasyWin-приложение.

ПРЕДУПРЕЖДЕНИЕ

Поскольку в MEMERR выделяется вся доступная память, следует сохранить все документы и закрыть другие приложения до запуска программы на выполнение. Хоть это и маловероятно, однако MEMERR При выполнении может привести к краху DOS, Windows и администраторов памяти. (Я уже сталкивался с подобными неприятностям.) Это справедливо вдвойне, если вы модифицировали параметры программы. Будьте особенно осторожны при запуске этой программ**.

Листинг 15.19. MEMERR.CPP (исключительные ситуации и оператор new)

«include <iostream.h>

«include <new.h>

«include <except.h>

«include <cstring.h>

«include <stdlib.h>

struct q { int ia[1024];

char *pool; int main() struct q *qp;

try {

pool = new char[512]; // Зарезервировать немного памяти

catch (xalloc) { cout «"Not enough RAM to run program" << endl;

catch (xalloc x) {

for (;;) { // Выполнять следующие операторы "до бесконечности"
try { // Блок try для обработки исключительных ситуаций

qp = new q; // Выделить новую структуру типа q

cout «qp «'\п';// Отобразить адрес структуры

delete pool;

cout «"Error: '

exit(-1);

}

}

return 0; }

В программе MEMERR показано, что необходимо быть особенно осмотрительными при обработке ошибок не­хватки памяти. В программе в "бесконечном" цикле выделяются 2048-байтовые структуры типа q до тех пор, по­ка оператор new не возбудит исключительную ситуацию xalloc. Оператор catch, обрабатывающий эту ошибку, вызывает функцию why() (наследованную из класса xmsg) для отображения сообщения о возникшей проблеме.

Проблема заключается в том, что при истощении ресурсов свободной памяти нет никакой гарантии, что сработает оператор вывода в строке 32. Действительно, если закончилась свободная память, вы должны быть особенно осторожны с операциями ввода-вывода и любыми другими действиями, для выполнения которых мо­жет потребоваться некоторое количество памяти. В таких случаях вызов функции why О и простой вывод строки могут привести к краху системы и потере данных.

Решение этой проблемы — резервирование небольшого количества памяти в начале выполнения програм­мы. В случае возникновения ошибки нехватки памяти следует освободить этот резерв, чтобы дать возмож­ность операторам вывода получить немного свободной памяти для работы. После этого вы можете завершить выполнение программы или, если вы решили продолжить работу, заново зарезервировать участок памяти на случай возникновения в будущем проблем с выделением свободной памяти.

ПРЕДУПРЕЖДЕНИЕ

Если вы хотите испробовать нее, о чем говорилось в предыдущем разделе, измените размер резервируемой памяти с 512 байт на 1 или 2. скомпилируйте программу заново и запустите на выполнение. Модифицированная программа может привести к краху системы с сообщением General Protect Fault (GPF) (общее нарушение защиты) и к потере данных. Этот эксперимент следует проводить крайне осторожно.



Поделиться:


Последнее изменение этой страницы: 2016-08-01; просмотров: 208; Нарушение авторского права страницы; Мы поможем в написании вашей работы!

infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 3.145.206.169 (0.097 с.)