Справочник функций

Ваш аккаунт

Войти через: 
Забыли пароль?
Регистрация
Информацию о новых материалах можно получать и без регистрации:

Почтовая рассылка

Подписчиков: -1
Последний выпуск: 19.06.2015

Обработка исключений в C++

Источник: http://src.fitkursk.ru/

Введение

Язык С представляет программисту очень ограниченные возможности обработки исключений, возникших при работе программы. В этом отношении С++ намного развитее С. Здесь у программиста существенно большие возможности по непосредственной обработке исключений. Комитет по разработке стандартов С++ предоставил очень простую, но мощную форму обработки исключений.

Темные дни С

Типичная функция, написанная на С, выглядит примерно так:

long DoSomething()
{
  long *a, c;
  FILE *b;
  a = malloc(sizeof(long) * 10);
  if (a == NULL)
	return 1;
  b = fopen("something.bah", "rb");
  if (b == NULL) {
	free(a);
	return 2;
  }
  fread(a, sizeof(long), 10, b);
  if (a[0] != 0x10) {
	free(a);
	fclose(b);
	return 3;
  }
  fclose(b);
  c = a[1];
  free(a);
  return c;
}

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

Try-catch-throw

Давайте же разберем основы обработки исключений в С++. Чтобы комфортно работать с исключениями в С++ вам нужно знать лишь три ключевых слова:

  • try (пытаться) - начало блока исключений;
  • catch (поймать) - начало блока, "ловящего" исключение;
  • throw (бросить) - ключевое слово, "создающее" ("возбуждающее") исключение.

А теперь пример, демонстрирующий, как применить то, что вы узнали:

void func()
{
  try
  {
    throw 1;
  }
  catch(int a)
  {
    cout << "Caught exception number:  " << a << endl;
    return;
  }
  cout << "No exception detected!" << endl;
  return;
}

Если выполнить этот фрагмент кода, то мы получим следующий результат:

Caught exception number:  1

Теперь закоментируйте строку throw 1; и функция выдаст такой результат:

No exception detected!

Как видите все очень просто, но если это применить с умом, такой подход покажется вам очень мощным средством обработки ошибок. Catch может "ловить" любой тип данных, так же как и throw может "кинуть" данные любого типа. Т.е. throw AnyClass(); будет правильно работать, так же как и catch (AnyClass &d) {};.

Как уже было сказано, catch может "ловить" данные любого типа, но вовсе не обязательно при это указывать переменную. Т.е. прекрасно будет работать что-нибудь типа этого:

catch(dumbclass) { }

так же, как и

catch(dumbclass&) { }

Так же можно "поймать" и все исключения:

catch(...) { }

Троеточие в этом случае показывает, что будут пойманы все исключения. При таком подходе нельзя указать имя переменной. В случае, если "кидаются" данные нестандартного типа (экземпляры определенных вами классов, структур и т.д.), лучше "ловить" их по ссылке, иначе вся "кидаемая" переменная будет скопирована в стек вместо того, чтобы просто передать указатель на нее. Если кидаются данные нескольких типов и вы хотите поймать конкретную переменную (вернее, переменную конкретного типа), то можно использовать несколько блоков catch, ловящих "свой" тип данных:

try {
  throw 1;
//  throw 'a';
}
catch (long b) {
  cout << "пойман тип long:  " << b << endl;
}
catch (char b) {
  cout << "пойман тип char:  " << b << endl;
}"

Создание" исключений

Когда возбуждается исключительная ситуация, программа просматривает стек функций до тех пор, пока не находит соответствующий catch. Если оператор catch не найден, STL будет обрабатывать исключение в стандартном обработчике, который делает все менее изящно, чем могли бы сделать вы, показывая какие-то непонятные (для конечного пользователя) сообщения и обычно аварийно завершая программу.

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

Перегрузка глобальных операторов new/delete

А сейчас хотелось бы отправить вас к статье "Как обнаружить утечку памяти". В ней рассказывается, как обнаружить неправильное управление распределением памяти в вашей программе. Вы можете спросить, при чем тут перегрузка операторов? Если перегрузить стандартные new и delete, то открываются широкие возможности по отслеживанию ошибок (причем ошибок часто критических) с помощью исключений. Например:


char *a;
try
{
  a = new char[10];
}
catch (...){
  // a не создан - обработать ошибку распределения памяти, 
  // выйти из программы и т.п.
}
// a успешно создан, продолжаем выполнение

Это, на первый взгляд, кажется длиннее, чем стандартная проверка в С "а равен NULL?", однако если в программе выделяется десяток динамических переменных, то такой метод оправдывает себя.

Операторы throw без параметров

Итак, мы увидели, как новый метод обработки ошибок удобен и прост. Блок try-catch может содержать вложенные блоки try-catch и если не будет определено соответствующего оператора catch на текущем уровен вложения, исключение будет поймано на более высоком уровне. Единственная вещь, о которой вы должны помнить, - это то, что операторы, следующие за throw, никогда не выполнятся.

try
{
  throw;
  // ни один оператор, следующий далее (до закрывающей скобки) 
  // выполнен не будет
}
catch(...)
{
  cout << "Исключение!" << endl;
}

Такой метод может применяться в случаях, когда не нужно передавать никаких данных в блок catch.

Приложение

Приведем пример, как все вышеизложенное может быть использовано в конкретном приложении. Преположим, у вас в программе есть класс cMain и экземпляр этого класса Main: class cMain { public: bool Setup(); bool Loop(); // Основной цикл программы void Close(); }; cMain Main;

А в функции main() или WinMain() вы можете использовать этот класс как-нибудь так:

try
{
  Main.Setup();
  Main.Loop();
  Main.Close();
}
catch (Exception &e)
{
  // использование класса, ведущего лог.
  log("Exception thrown:  %s", e.String()); 

  // Показываем сообщение об ошибке и закрываем приложение.
}

Основной цикл программы может выглядеть примерно так:

while (AppActive)
{
  try
  {
    // какие-то действия
  }
  catch (Exception &e)
  {
    /* Если исключение критическое, типа ошибки памяти,
     посылаем исключение дальше, в main(), оператором throw e;
     или просто throw.
     Если исключение некритично, обрабатываем его и
     возвращаемся в основной цикл. */  
  }
}

Заключение

Метод обработки исключений, приведенный в статье, является удобным и мощным средством, однако только вам решать, использовать его или нет. Одно можно скачать точно - приведенный метод облегчит вам жизнь. Если хотите узнать об исключениях чуть больше, посмотрите публикацию Deep C++ на сервере MSDN.

Оставить комментарий

Комментарий:
можно использовать BB-коды
Максимальная длина комментария - 4000 символов.
 

Комментарии

1.
326
19 ноября 2005 года
sadovoya
757 / / 19.11.2005
+22 / -7
Мне нравитсяМне не нравится
13 мая 2012, 16:28:18
Если я правильно понимаю, то в случае ошибки, все, что сделано в try{}, включая вызовы внешних функций из него, полностью исчезает, т.е. происходит полный "откат" к состоянию до выполнения этого блока. Если так, то это очень здорово. Или, часть кода в try{}, которая возникла до момента ошибки все-таки не "откатывается"? Поясните, если кто знает, я новичок в этой теме.
2.
590
03 апреля 2006 года
Gigahard
223 / / 03.04.2006
+11 / -6
Мне нравитсяМне не нравится
4 июля 2007, 14:12:12
Что подразумевается под строками "Однако более важным моментом является то, что пока просматривается стек функций, вызываются деструкторы всех локальных классов, так что вам не нужно забодиться об освобождении памяти и т.п."? Если у меня в программном коде создано несколько объектов классов, а потом по ходу исполнения происходит исключительная ситуация, то что, все ранее созданные объекты уничтожаются? А если у меня в обработке этого исключения стоит ветвление по повторной попытке выполнения операции и отмене?
Т.е. если первый раз какая то операция вызвала исключение, а второй раз завершилась успешно, то что будет с данными определенными вне блоков try{} catch{}?.
Или все же под локальностью подразумевается как раз тело блока try{}?
Реклама на сайте | Обмен ссылками | Ссылки | Экспорт (RSS) | Контакты
Добавить статью | Добавить исходник | Добавить хостинг-провайдера | Добавить сайт в каталог